projects/wms-framework/src/lib/baseframework/collections.ts
SimpleList Class
Properties |
|
Methods |
Accessors |
constructor(arg?: unknown)
|
||||||
Parameters :
|
Protected _internalArray |
Type : T[]
|
Default value : []
|
Protected Static interfacesInitialized |
Default value : false
|
add | ||||||
add(value: T)
|
||||||
Adds an element to the list
Parameters :
Returns :
void
|
addRange | ||||||
addRange(range: Iterable<T>)
|
||||||
Adds elements of an iterable to the list
Parameters :
Returns :
void
|
clear |
clear()
|
Clears the list
Returns :
void
|
contains | ||||||
contains(value: T)
|
||||||
Check if a value is contained in the list
Parameters :
Returns :
boolean
|
copyTo | |||||||||
copyTo(target: T[], index: number)
|
|||||||||
Copies the list to a target array
Parameters :
Returns :
void
|
forEach | ||||||
forEach(action: (e: T) => void)
|
||||||
Performs an action on each element of the list
Parameters :
Returns :
void
|
getItem | ||||||
getItem(index: number)
|
||||||
Parameters :
Returns :
T
|
indexOf | ||||||
indexOf(value: T)
|
||||||
Index of value in list
Parameters :
Returns :
any
|
Protected Static initializeSupportedInterfaces |
initializeSupportedInterfaces()
|
Returns :
void
|
insert | |||||||||
insert(index: number, value: T)
|
|||||||||
Inserts element in index
Parameters :
Returns :
void
|
insertRange | |||||||||
insertRange(position: number, range: Iterable<T>)
|
|||||||||
Inserts elements of an interable to the list in a position
Parameters :
Returns :
void
|
remove | ||||||
remove(value: T)
|
||||||
Removes a value from the list
Parameters :
Returns :
boolean
|
Public removeAll | ||||||
removeAll(predicate: (e: T) => void)
|
||||||
Removes all elements from the collection by a predicate
Parameters :
Returns :
number
|
removeAt | ||||||
removeAt(index: number)
|
||||||
Removes element at index
Parameters :
Returns :
void
|
setItem | |||||||||
setItem(index: number, value: T)
|
|||||||||
Parameters :
Returns :
void
|
sort |
sort()
|
Sorts the list
Returns :
any
|
sort | ||||||
sort(comparer: IComparer<T>)
|
||||||
Parameters :
Returns :
any
|
sort | ||||||
sort(comparison: (x: T,y: T) => void)
|
||||||
Parameters :
Returns :
any
|
sort | ||||||
sort(p?: any)
|
||||||
Parameters :
Returns :
void
|
Public toArray |
toArray()
|
Converts the list to an array
Returns :
{}
|
()
|
Returns :
Iterator<T, any, undefined>
|
internalArray | ||||||
getinternalArray()
|
||||||
Gets or sets the internal array.
Returns :
T[]
|
||||||
setinternalArray(value: T[])
|
||||||
Parameters :
Returns :
void
|
count |
getcount()
|
Gets the count of elements in list
Returns :
number
|
import { INotifyPropertyChanged } from '../basecomponentmodel/INotifyPropertyChanged';
import { supportedInterfacesCompatibilityMetadataKey } from '../decorators/ClassInfo';
import { Debugger } from '../diagnostics/Debugger';
import { CancelEventArgs } from '../helpers/CancelEventArgs';
import { SubscriptionEvent } from '../utils/SubscriptionEvent';
import { InvalidOperationException } from './Exceptions';
import { SortDescription } from './SortDescription';
/**
* IEqualityComparer interface
*
* @export
* @interface IEqualityComparer
* @template T
* @wInterface System.Collections.Generic.IEqualityComparer`1
* @wNetSupport
*/
export interface IEqualityComparer<T> {
Equals(x: T, y: T): boolean;
GetHashCode(x: T): number;
}
/**
* IComparer interface
*
* @export
* @interface IComparer
* @template T
* @wInterface System.Collections.Generic.IComparer`1
* @wNetSupport
*/
export interface IComparer<T> {
Compare(x: T, y: T): number;
}
/**
* IComparable interface
*
* @export
* @interface IComparable
* @template T
* @wInterface System.IComparable`1
* @wNetSupport
*/
export interface IComparable<T> {
CompareTo(x: T): number;
}
/**
* IEquatable interface
*
* @export
* @interface IEquatable
* @template T
* @wInterface System.IEquatable`1
* @wNetSupport
*/
export interface IEquatable<T> {
Equals(other: T): boolean;
}
export interface ISupportCollection<T> {
internalArray: Array<T>;
}
/**
* IOrderedIterable interface
*
* @export
* @interface IOrderedIterable
* @extends {Iterable<V>}
* @template V
* @wInterface System.Linq.IOrderedEnumerable`1
* @wNetSupport
*/
export interface IOrderedIterable<V> extends Iterable<V> {
/**
* Creates ordered iterable
*
* @template K
* @param {(e: V) => K} keySelector
* @param {IComparer<K>} comparer
* @param {boolean} descending
* @returns {IOrderedIterable<V>}
* @memberof IOrderedIterable
* @wMethod CreateOrderedEnumerable
*/
CreateOrderedIterable<K>(
keySelector: (e: V) => K,
comparer: IComparer<K>,
descending: boolean
): IOrderedIterable<V>;
}
export interface UnTypedIterable {}
export interface IUntypedCollection {}
/**
* ICollection interface
*
* @export
* @interface ICollection
* @extends {Iterable<T>}
* @extends {ISupportCollection<T>}
* @template T
* @wInterface System.Collections.Generic.ICollection`1
* @wNetSupport
*/
export interface ICollection<T> extends Iterable<T>, ISupportCollection<T> {
/**
* The number of elemenst in the collection
*
* @type {number}
* @memberof ICollection
* @wProperty Count
*/
count: number;
/**
* Adds a value to the collection
*
* @param {T} value
* @memberof ICollection
* @wMethod Add
*/
add(value: T): void;
/**
* Clears the collection
*
* @memberof ICollection
* @wMethod Clear
*/
clear(): void;
/**
* Check if a element is contained in the collection
*
* @param {T} value
* @returns {boolean}
* @memberof ICollection
* @wMethod Contains
*/
contains(value: T): boolean;
/**
* Removes the element from the collection
*
* @param {T} value
* @returns {boolean}
* @memberof ICollection
*/
remove(value: T): boolean;
copyTo(target: Array<T>, index: number): void;
}
/**
* IPagedCollectionView interface
*
* @export
* @interface IPagedCollectionView
* @wInterface System.ComponentModel.IPagedCollectionView
*/
export interface IPagedCollectionView {
CanChangePage: boolean;
IsPageChanging: boolean;
ItemCount: number;
PageIndex: number;
PageSize: number;
TotalItemCount: number;
PageChanged: SubscriptionEvent<(e: any, args: CollectionChangeInfo) => void>;
PageChanging: SubscriptionEvent<
(e: any, args: PageChangingEventArgs) => void
>;
MoveToFirstPage(): boolean;
MoveToLastPage(): boolean;
MoveToNextPage(): boolean;
MoveToPage(pageIndex: number): boolean;
MoveToPreviousPage(): boolean;
}
/**
* IList interface
*
* @export
* @interface IList
* @extends {ICollection<T>}
* @template T
* @wInterface System.Collections.Generic.IList`1
* @wNetSupport
*/
export interface IList<T> extends ICollection<T> {
getItem(index: number): T;
setItem(index: number, value: T);
/**
* Gets the index of an element in the list
*
* @param {T} value
* @returns {number}
* @memberof IList
* @wMethod IndexOf
*/
indexOf(value: T): number;
/**
* Insert element in position
*
* @param {number} index
* @param {T} value
* @memberof IList
* @wMethod Insert
*/
insert(index: number, value: T);
/**
* Removes element at index
*
* @param {number} index
* @memberof IList
* @wMethod RemoveAt
*/
removeAt(index: number);
/**
* Sorts the list
*
* @memberof IList
* @wMethod Sort
*/
sort();
/**
* Sorts the list by comparer
*
* @param {IComparer<T>} comparer
* @memberof IList
* @wMethod Sort
*/
sort(comparer: IComparer<T>);
}
/**
* SimpleList Class
*
* @export
* @class SimpleList
* @implements {IList}
* @wType System.Collections.Generic.List`1
* @wNetSupport
*/
export class SimpleList<T> implements IList<T> {
protected static interfacesInitialized = false;
protected _internalArray: T[] = [];
protected static initializeSupportedInterfaces(): void {
if (!SimpleList.interfacesInitialized) {
const setMetadataInfo = Reflect.metadata(
supportedInterfacesCompatibilityMetadataKey,
[
'System.Collections.Generic.ICollection`1',
'System.Collections.Generic.IList`1',
'System.Collections.Generic.IList',
'System.Collections.IList',
]
);
setMetadataInfo(SimpleList);
SimpleList.interfacesInitialized = true;
}
}
constructor();
constructor(initialValues: Iterable<T>);
constructor(capacity: number);
constructor(arg?: unknown) {
const newLocal = typeof arg;
// capacity argument still not supported
if (newLocal != 'undefined' && newLocal != 'number') {
const initialValues = arg as Iterable<T>;
this.internalArray = [...initialValues];
}
SimpleList.initializeSupportedInterfaces();
}
/**
* Gets or sets the internal array.
*
* @type {T[]}
* @memberof SimpleList
*/
get internalArray(): T[] {
// eslint-disable-next-line no-underscore-dangle
return this._internalArray;
}
set internalArray(value: T[]) {
// eslint-disable-next-line no-underscore-dangle
this._internalArray = value;
}
getItem(index: number): T {
if (index >= this.internalArray.length || index < 0) {
throw new Error('Invalid index');
}
return this.internalArray[index];
}
setItem(index: number, value: T) {
if (index >= this.internalArray.length || index < 0) {
throw new Error('Invalid index');
}
this.internalArray[index] = value;
}
/**
* Index of value in list
*
* @param {T} value
* @returns
* @memberof SimpleList
* @wMethod IndexOf
*/
indexOf(value: T) {
return this.internalArray.indexOf(value);
}
/**
* Inserts element in index
*
* @param {number} index
* @param {T} value
* @memberof SimpleList
* @wMethod Insert
*/
insert(index: number, value: T) {
this.internalArray.splice(index, 0, value);
}
/**
* Removes element at index
*
* @param {number} index
* @memberof SimpleList
* @wMethod RemoveAt
*/
removeAt(index: number) {
this.internalArray.splice(index, 1);
}
/**
* Gets the count of elements in list
*
* @readonly
* @type {number}
* @memberof SimpleList
* @wProperty Count
*/
get count(): number {
return this.internalArray.length;
}
/**
* Adds an element to the list
*
* @param {T} value
* @memberof SimpleList
* @wMethod Add
*/
add(value: T): void {
this.internalArray.push(value);
}
/**
* Clears the list
*
* @memberof SimpleList
* @wMethod Clear
*/
clear(): void {
this.internalArray.splice(0, this.internalArray.length);
}
/**
* Check if a value is contained in the list
*
* @param {T} value
* @returns {boolean}
* @memberof SimpleList
* @wMethod Contains
*/
contains(value: T): boolean {
return this.internalArray.indexOf(value) !== -1;
}
/**
* Removes a value from the list
*
* @param {T} value
* @returns {boolean}
* @memberof SimpleList
* @wMethod Remove
*/
remove(value: T): boolean {
const idx = this.internalArray.indexOf(value);
if (idx !== -1) {
this.internalArray.splice(idx, 1);
return true;
} else {
return false;
}
}
/**
* Performs an action on each element of the list
*
* @param {(e: T) => void} action
* @memberof SimpleList
* @wMethod ForEach
*/
forEach(action: (e: T) => void): void {
for (const obj of this.internalArray) {
action(obj);
}
}
/**
* Adds elements of an iterable to the list
*
* @param {Iterable<T>} range
* @memberof SimpleList
* @wMethod AddRange
*/
addRange(range: Iterable<T>): void {
for (const obj of range) {
this.add(obj);
}
}
/**
* Inserts elements of an interable to the list in a position
*
* @param {number} position
* @param {Iterable<T>} range
* @memberof SimpleList
* @wMethod InserRange
*/
insertRange(position: number, range: Iterable<T>): void {
if (!(position < this.internalArray.length && position >= 0)) {
throw new Error('Invalid position');
}
this.internalArray.splice(position, 0, ...range);
}
/**
* Sorts the list
*
* @memberof SimpleList
* @wMethod Sort
*/
sort();
sort(comparer: IComparer<T>);
sort(comparison: (x: T, y: T) => number);
sort(p?: any) {
if (typeof p === 'object') {
this.internalArray = this.internalArray.sort(p.Compare);
} else if (typeof p === 'function') {
this.internalArray = this.internalArray.sort(p);
} else {
if (
this.internalArray.length > 0 &&
typeof this.internalArray[0] === 'object'
) {
this.internalArray.sort((a: any, b: any) => {
if (a?.CompareTo && b?.CompareTo) {
return a.CompareTo(b);
} else {
return 0;
}
});
} else {
this.internalArray = this.internalArray.sort();
}
}
}
/**
* Copies the list to a target array
*
* @param {T[]} target
* @param {number} index
* @memberof SimpleList
* @wMethod CopyTo
*/
copyTo(target: T[], index: number): void {
for (let i = index; i < target.length; i++) {
target[i] = this.internalArray[i];
}
}
/**
* Removes all elements from the collection by a predicate
*
* @param {(e: T) => boolean} predicate
* @returns
* @memberof SimpleList
* @wMethod RemoveAll
*/
public removeAll(predicate: (e: T) => boolean) {
let removed = 0;
for (let i = this.internalArray.length - 1; i >= 0; i--) {
if (predicate(this.internalArray[i])) {
this.removeAt(i);
removed++;
}
}
return removed;
}
/**
* Converts the list to an array
*
* @returns
* @memberof SimpleList
* @wMethod ToArray
*/
public toArray() {
return [...this.internalArray];
}
[Symbol.iterator](): Iterator<T, any, undefined> {
return this.internalArray[Symbol.iterator]();
}
}
/**
* A collection of sort descriptions
*
* @export
* @class SortDescriptionCollection
* @extends {SimpleList<SortDescription>}
* @wType System.ComponentModel.SortDescriptionCollection
*/
export class SortDescriptionCollection
extends SimpleList<SortDescription>
implements INotifyCollectionChanged
{
CollectionChanged: SubscriptionEvent<
(e: any, args: CollectionChangeInfo) => void
> = new SubscriptionEvent();
/**
* Raises the CollectionChanged event.
*
* @param {CollectionChangeInfo} info
* @memberof SortDescriptionCollection
*/
onCollectionChanged(info: CollectionChangeInfo): void {
this.CollectionChanged.fire([this, info]);
}
/**
* Inserts a new element at the specified position.
*
* @param {number} index
* @param {T} value
* @memberof SortDescriptionCollection
*/
insert(index: number, value: SortDescription): void {
super.insert(index, value);
const info = new CollectionChangeInfo(CollectionChangeAction.Add);
info.NewItems = new SimpleList([value]);
info.NewStartingIndex = index;
this.onCollectionChanged(info);
}
/**
* Removes the first element from the collection which matches the given value.
*
* @param {T} value
* @return {*} {boolean}
* @memberof SortDescriptionCollection
*/
remove(value: SortDescription): boolean {
const idx = this.internalArray.indexOf(value);
if (idx === -1) {
return false;
}
const result = super.remove(value);
const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
info.OldItems = new SimpleList([value]);
info.OldStartingIndex = idx;
this.onCollectionChanged(info);
return result;
}
/**
* Removes the element at the given position.
*
* @param {number} index
* @memberof SortDescriptionCollection
*/
removeAt(index: number): void {
const oldItem = super.getItem(index);
super.removeAt(index);
const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
info.OldItems = new SimpleList([oldItem]);
info.OldStartingIndex = index;
this.onCollectionChanged(info);
}
/**
* Adds the given element at the end of the collection.
*
* @param {T} value
* @memberof SortDescriptionCollection
*/
add(value: SortDescription): void {
super.add(value);
const info = new CollectionChangeInfo(CollectionChangeAction.Add);
info.NewItems = new SimpleList([value]);
info.NewStartingIndex = this.count - 1;
this.onCollectionChanged(info);
}
/**
* Removes all elements from the collection.
*
* @memberof SortDescriptionCollection
*/
clear(): void {
super.clear();
this.onCollectionChanged(
new CollectionChangeInfo(CollectionChangeAction.Reset)
);
}
/**
* Replace the element at the given position with the given element.
*
* @param {number} index
* @param {T} value
* @memberof SortDescriptionCollection
* @wMethod SetItem
*/
setItem(index: number, value: SortDescription): void {
const oldItem = super.getItem(index);
super.setItem(index, value);
const info = new CollectionChangeInfo(CollectionChangeAction.Replace);
info.OldItems = new SimpleList([oldItem]);
info.NewItems = new SimpleList([value]);
info.NewStartingIndex = index;
this.onCollectionChanged(info);
}
}
/**
* Reports changes in a collection.
*
* @export
* @class CollectionChangeInfo
* @wType System.Collections.Specialized.NotifyCollectionChangedEventArgs
*/
export class CollectionChangeInfo {
/**
* The items added or changed in the collection
*
* @type {IList<any>}
* @memberof CollectionChangeInfo
*/
public NewItems: IList<any> = null;
/**
* The index where the change occurred.
*
* @type {number}
* @memberof CollectionChangeInfo
*/
public NewStartingIndex = -1;
/**
* The items that where removed from the collection.
*
* @type {IList<any>}
* @memberof CollectionChangeInfo
*/
public OldItems: IList<any> = null;
/**
* The index where a replace action or remove occurred.
*
* @type {number}
* @memberof CollectionChangeInfo
*/
public OldStartingIndex = -1;
/**
* Creates an instance of CollectionChangeInfo.
*
* @param {CollectionChangeAction} action
* @memberof CollectionChangeInfo
*/
constructor(public action: CollectionChangeAction) {}
}
/**
* CollectionChangeAction enum
*
* @export
* @enum {number}
* @wEnum System.Collections.Specialized.NotifyCollectionChangedAction
*/
export enum CollectionChangeAction {
Add,
Remove,
Replace,
Reset,
}
/**
* INotifyCollectionChanged interface
*
* @export
* @interface INotifyCollectionChanged
* @wInterface System.Collections.Specialized.INotifyCollectionChanged
*/
export interface INotifyCollectionChanged {
CollectionChanged: SubscriptionEvent<
(e: any, args: CollectionChangeInfo) => void
>;
}
/**
* ObjectModelCollection class
*
* @export
* @class ObjectModelCollection
* @extends {SimpleList<T>}
* @template T
* @wType System.Collections.ObjectModel.Collection`1
*/
export class ObjectModelCollection<T> extends SimpleList<T> {}
/**
* ItemDataRequestedEventArgs class
*
* @export
* @class ItemDataRequestedEventArgs
* @wType Infragistics.Collections.ItemDataRequestedEventArgs
*/
export class ItemDataRequestedEventArgs {
/**
* FilterConditions property
*
* @type {*}
* @memberof ItemDataRequestedEventArgs
*/
FilterConditions: any = null;
/**
* ItemsCount property
*
* @type {number}
* @memberof ItemDataRequestedEventArgs
*/
ItemsCount: number = null;
/**
* SortDescriptions Property
*
* @type {*}
* @memberof ItemDataRequestedEventArgs
*/
SortDescriptions: any = null;
/**
* StartIndex property.
*
* @type {number}
* @memberof ItemDataRequestedEventArgs
*/
StartIndex: number = null;
}
/**
* PageChangingEventArgs class
*
* @export
* @class PageChangingEventArgs
* @wType System.ComponentModel.PageChangingEventArgs
*/
export class PageChangingEventArgs extends CancelEventArgs {
/**
* New page index internal field
*
* @private
* @type {number}
* @memberof PageChangingEventArgs
*/
private newPageIndex: number;
/**
*Creates an instance of PageChangingEventArgs.
* @param {number} newPageIndex
* @memberof PageChangingEventArgs
*/
constructor(newPageIndex: number) {
super();
this.newPageIndex = newPageIndex;
}
/**
* StartIndex property.
*
* @type {number}
* @memberof PageChangingEventArgs
*/
get NewPageIndex(): number {
return this.newPageIndex;
}
}
/**
* Collection which reports changes to its observers.
*
* @export
* @class ObservableCollection
* @extends {ObjectModelCollection<T>}
* @implements {INotifyPropertyChanged}
* @template T
* @wType System.Collections.ObjectModel.ObservableCollection`1
*/
export class ObservableCollection<T>
extends ObjectModelCollection<T>
implements INotifyCollectionChanged, INotifyPropertyChanged
{
/**
* Flag to indicate if supported interfaces have been initialized.
*
* @private
* @static
* @memberof ObservableCollection
*/
protected static interfacesInitialized = false;
/**
* Event to indicate that a property have changed.
*
* @memberof ObservableCollection
*/
public PropertyChanged: SubscriptionEvent<
(e: any, args: { PropertyName: string }) => void
> = new SubscriptionEvent();
/**
* Event to indicate that the collection have changed.
*
* @memberof ObservableCollection
*/
public CollectionChanged: SubscriptionEvent<
(e: any, args: CollectionChangeInfo) => void
> = new SubscriptionEvent();
/**
* Event AddNewItem have changed.
*
* @memberof ObservableCollection
* @wIgnore
*/
public AddNewItem: SubscriptionEvent<
(e: any, args: CollectionChangeInfo) => void
> = new SubscriptionEvent();
/**
* Creates an instance of ObservableCollection.
*
* @param {Iterable<T>} [initial]
* @memberof ObservableCollection
*/
constructor(initial?: Iterable<T>) {
super();
if (typeof initial !== 'undefined' && initial !== null) {
this.internalArray = [...initial];
}
ObservableCollection.initializeSupportedInterfaces();
}
/**
* Initialize the interfaces supported by this collection.
*
* @private
* @static
* @memberof ObservableCollection
*/
protected static initializeSupportedInterfaces(): void {
if (!ObservableCollection.interfacesInitialized) {
const setMetadataInfo = Reflect.metadata(
supportedInterfacesCompatibilityMetadataKey,
[
'System.ComponentModel.INotifyPropertyChanged',
'System.Collections.IEnumerable',
'System.Collections.Generic.IList`1',
'System.Collections.IList',
'System.Collections.Specialized.INotifyCollectionChanged',
]
);
setMetadataInfo(ObservableCollection);
ObservableCollection.interfacesInitialized = true;
}
}
/**
* Inserts a new element at the specified position.
*
* @param {number} index
* @param {T} value
* @memberof ObservableCollection
*/
insert(index: number, value: T): void {
super.insert(index, value);
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Add
);
changeEventInfo.NewItems = new SimpleList([value]);
changeEventInfo.NewStartingIndex = index;
this.onCollectionChanged(changeEventInfo);
this.onPropertyChanged({ PropertyName: 'Count' });
}
/**
* Removes the first element from the collection which matches the given value.
*
* @param {T} value
* @return {*} {boolean}
* @memberof ObservableCollection
*/
remove(value: T): boolean {
const idx = this.internalArray.indexOf(value);
if (idx === -1) {
return false;
}
const result = super.remove(value);
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Remove
);
changeEventInfo.OldItems = new SimpleList([value]);
changeEventInfo.OldStartingIndex = idx;
this.onCollectionChanged(changeEventInfo);
this.onPropertyChanged({ PropertyName: 'Count' });
return result;
}
/**
* Removes the element at the given position.
*
* @param {number} index
* @memberof ObservableCollection
*/
removeAt(index: number): void {
const oldItem = super.getItem(index);
super.removeAt(index);
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Remove
);
changeEventInfo.OldItems = new SimpleList([oldItem]);
changeEventInfo.OldStartingIndex = index;
this.onCollectionChanged(changeEventInfo);
this.onPropertyChanged({ PropertyName: 'Count' });
}
/**
* Adds the given element at the end of the collection.
*
* @param {T} value
* @memberof ObservableCollection
*/
add(value: T): void {
super.add(value);
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Add
);
changeEventInfo.NewItems = new SimpleList([value]);
changeEventInfo.NewStartingIndex = this.count - 1;
this.onCollectionChanged(changeEventInfo);
this.onPropertyChanged({ PropertyName: 'Count' });
}
/**
* Adds elements of an iterable to the list
*
* @param {Iterable<T>} range
* @memberof ObservableCollection
* @wIgnore
*/
silentAddRange(range: Iterable<T>) {
for (const obj of range) {
super.add(obj);
}
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Add
);
changeEventInfo.NewItems = new SimpleList([range]);
changeEventInfo.NewStartingIndex = this.count - 1;
this.onCollectionChanged(changeEventInfo);
this.onPropertyChanged({ PropertyName: 'Count' });
}
/**
* Removes all elements from the collection.
*
* @memberof ObservableCollection
*/
clear(): void {
super.clear();
this.onCollectionChanged(
new CollectionChangeInfo(CollectionChangeAction.Reset)
);
this.onPropertyChanged({ PropertyName: 'Count' });
}
/**
* Replace the element at the given position with the given element.
*
* @param {number} index
* @param {T} value
* @memberof ObservableCollection
* @wMethod SetItem
*/
setItem(index: number, value: T): void {
const oldItem = super.getItem(index);
super.setItem(index, value);
const changeEventInfo = new CollectionChangeInfo(
CollectionChangeAction.Replace
);
changeEventInfo.OldItems = new SimpleList([oldItem]);
changeEventInfo.NewItems = new SimpleList([value]);
changeEventInfo.NewStartingIndex = index;
this.onCollectionChanged(changeEventInfo);
}
/**
* Removes all elements from the collection.
*
* @protected
* @memberof ObservableCollection
* @wMethod ClearItems
*/
protected clearItems(): void {
this.clear();
}
/**
* Trigger a `CollectionChanged` event.
*
* @protected
* @param {CollectionChangeInfo} info
* @memberof ObservableCollection
* @wMethod OnCollectionChanged
*/
protected onCollectionChanged(info: CollectionChangeInfo): void {
this.CollectionChanged.fire([this, info]);
}
/**
* Trigger a `PropertyChanged` event.
*
* @protected
* @param {{ PropertyName: string }} info
* @memberof ObservableCollection
* @wMethod OnPropertyChanged
*/
protected onPropertyChanged(info: { PropertyName: string }): void {
this.PropertyChanged.fire([this, info]);
}
}
/**
* SimpleQueue class
*
* @export
* @class SimpleQueue
* @implements {Iterable<T>}
* @template T
* @wType System.Collections.Generic.Queue`1
*/
export class SimpleQueue<T> implements Iterable<T> {
public internalArray: T[] = [];
constructor(initialData?: Iterable<T>) {
if (typeof initialData !== 'undefined') {
this.internalArray = [...initialData];
}
}
/**
* Count the elements in queue
*
* @readonly
* @type {number}
* @memberof SimpleQueue
* @wProperty Count
*/
get count(): number {
return this.internalArray.length;
}
/**
* Enqueue an element in the queue
*
* @param {T} value
* @returns
* @memberof SimpleQueue
* @wMethod Enqueue
*/
public enqueue(value: T) {
return this.internalArray.push(value);
}
/**
* Dequeue an element of the queue
*
* @returns {T}
* @memberof SimpleQueue
* @wMethod Dequeue
*/
public dequeue(): T {
return this.internalArray.shift();
}
[Symbol.iterator](): Iterator<T, any, undefined> {
return this.internalArray[Symbol.iterator]();
}
}
/**
* ReadOnlyCollection class
*
* @export
* @class ReadonlyCollection
* @extends {SimpleList<T>}
* @template T
* @wType System.Collections.ObjectModel.ReadOnlyCollection`1
*/
export class ReadonlyCollection<T> extends SimpleList<T> {
constructor(contents?: Iterable<T>) {
super(contents);
}
// NOTE: this methods intentionally throw an exception, because
// it is not allowed to modify a read only collection.
insert(index: number, value: T) {
throw Error('Operation not supported');
}
remove(value: T): boolean {
throw Error('Operation not supported');
}
removeAt(index: number) {
throw Error('Operation not supported');
}
add(value: T): void {
throw Error('Operation not supported');
}
clear(): void {
throw Error('Operation not supported');
}
setItem(index: number, value: T) {
throw Error('Operation not supported');
}
}
//
// IterUtils
//
export function iuToArray<T>(iterable: Iterable<T>): Array<T> {
return [...iterable];
}
class SelectIteratorWrapper<T, K> implements Iterator<K> {
private index = 0;
constructor(
private innerIterator: Iterator<T>,
private funcWithIndex?: (e: T, i?: number) => K
) {}
next(): IteratorResult<K, any> {
const nextValue = this.innerIterator.next();
if (!nextValue.done) {
return {
value: this.funcWithIndex(nextValue.value, this.index++),
done: false,
};
}
return { value: undefined, done: true };
}
}
export function iuSelect<T, K>(
func: (e: T, i?: number) => K,
iterable: Iterable<T>
): Iterable<K> {
return {
[Symbol.iterator]() {
return new SelectIteratorWrapper(iterable[Symbol.iterator](), func);
},
};
}
class WhereIteratorWrapper<T> implements Iterator<T> {
constructor(
private innerIterator: Iterator<T>,
private predicate: (e: T) => boolean
) {}
next(value?: any): IteratorResult<T, any> {
let next = this.innerIterator.next();
while (!next.done && !this.predicate(next.value)) {
next = this.innerIterator.next();
}
return next;
}
}
export function iuWhere<T>(
predicate: (e: T) => boolean,
iterable: Iterable<T>
): Iterable<T> {
return {
[Symbol.iterator]() {
return new WhereIteratorWrapper(iterable[Symbol.iterator](), predicate);
},
};
}
export function iuFirst<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
): T {
if (
iterable instanceof Array &&
iterable.length > 0 &&
(typeof predicate === 'undefined' || predicate(iterable[0]))
) {
return iterable[0];
} else {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
while (!next.done) {
if (typeof predicate == 'undefined' || predicate(next.value)) {
return next.value;
}
next = iterator.next();
}
}
throw Error('Elements not available');
}
/**
* Returns the first element of the iterable for which predicate returns `true`,
* or a default value if the predicate returns `false` for every element.
* If no predicate is provided, returns the first element of the iterable, or
* a default value when the iterable is empty.
*
* @export
* @template T
* @param {Iterable<T>} iterable
* @param {(e: T) => boolean} [predicate]
* @param {T} [defaultValue=null]
* @return {*} {T}
*/
export function iuFirstOrDefault<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean,
defaultValue: T = null
): T {
if (
iterable instanceof Array &&
iterable.length > 0 &&
(typeof predicate === 'undefined' || predicate(iterable[0]))
) {
return iterable[0];
} else if (iterable != null) {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
while (!next.done) {
if (typeof predicate == 'undefined' || predicate(next.value)) {
return next.value;
}
next = iterator.next();
}
}
return defaultValue;
}
export function iuCount<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
): number {
if (iterable instanceof Array && !predicate) {
return iterable.length;
} else if (iterable instanceof SimpleList && !predicate) {
return iterable.count;
} else {
const iterator = iterable[Symbol.iterator]();
let i = 0;
let itResult = iterator.next();
while (!itResult.done) {
const current = itResult.value;
itResult = iterator.next();
if (predicate && !predicate(current)) {
continue;
}
i++;
}
return i;
}
}
class TakeIteratorWrapper<T> implements Iterator<T> {
constructor(private innerIterator: Iterator<T>, private count: number) {}
next(value?: any): IteratorResult<T, any> {
if (this.count > 0) {
this.count--;
const next = this.innerIterator.next();
return next;
} else {
return { value: undefined, done: true };
}
}
}
export function iuTake<T>(count: number, iterable: Iterable<T>): Iterable<T> {
return {
[Symbol.iterator]() {
return new TakeIteratorWrapper(iterable[Symbol.iterator](), count);
},
};
}
export function iuToList<T>(iterable: Iterable<T>): SimpleList<T> {
return new SimpleList(iterable);
}
export function iuLast<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
): T {
if (iterable instanceof Array && iterable.length > 0 && !predicate) {
return iterable[iterable.length - 1];
} else {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
let found = false;
let lastValue = null;
while (!next.done) {
if (predicate) {
if (predicate(next.value)) {
found = true;
lastValue = next.value;
}
} else {
found = true;
lastValue = next.value;
}
next = iterator.next();
}
if (!found) {
throw Error('Elements not available');
} else {
return lastValue;
}
}
}
export function iuLastOrDefault<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
): T {
if (iterable == null) {
throw Error('ArgumentNullException');
}
if (iterable instanceof Array && iterable.length > 0) {
if (predicate == undefined) {
return iterable[iterable.length - 1];
} else {
for (let i = iterable.length - 1; i >= 0; i--) {
if (predicate(iterable[i])) {
return iterable[i];
}
}
}
} else {
for (const val of Array.from(iterable).reverse()) {
if (predicate == undefined || predicate(val)) {
return val;
}
}
}
// here we have the problem of value types
// in this case we should not return null
// this work is still pending
return null;
}
export function iuElementAt<T>(iterable: Iterable<T>, index: number): T {
if (iterable instanceof Array && iterable.length > index && index >= 0) {
return iterable[index];
} else if (iterable instanceof SimpleList) {
return iterable.getItem(index);
} else {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
let found = false;
let lastValue;
let i = 0;
while (!next.done) {
if (i == index) {
found = true;
lastValue = next.value;
break;
}
next = iterator.next();
i++;
}
if (!found) {
throw Error('Elements not available');
} else {
return lastValue;
}
}
}
export function iuElementAtOrDefault<T>(
iterable: Iterable<T>,
index: number
): T {
if (iterable == null) {
throw Error('ArgumentNullException');
}
if (iterable instanceof Array && iterable.length > index && index >= 0) {
return iterable[index];
} else {
const iterator = iterable[Symbol.iterator]();
let result = iterator.next();
let i = 0;
while (!result.done) {
if (i == index) {
return result.value;
}
result = iterator.next();
i++;
}
}
return null;
}
export function iuAny<T>(iterable: Iterable<T>, predicate?: (e: T) => boolean) {
if (iterable instanceof Array && typeof predicate === 'undefined') {
return iterable.length > 0;
} else if (
iterable instanceof SimpleList &&
typeof predicate === 'undefined'
) {
return iterable.count > 0;
} else {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
if (typeof predicate === 'undefined') {
return !next.done;
} else {
let found = false;
while (!next.done) {
if (predicate(next.value)) {
found = true;
break;
}
next = iterator.next();
}
return found;
}
}
}
/**
* Verifies if a predicate is `true` for all the elements of an iterable
*
* @export
* @template T
* @param {Iterable<T>} iterable sequence to verify
* @param {(e: T) => boolean} [predicate] predicate
* @return {*} true if the predicate is valid for all elements of the sequence
*/
export function iuAll<T>(iterable: Iterable<T>, predicate: (e: T) => boolean) {
for (const element of iterable) {
if (!predicate(element)) {
return false;
}
}
return true;
}
export function iuSingle<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
) {
const errNotSingle = 'Sequence does not contain exactly one element';
const errNoElements =
'Sequence does not contain exactly one element that satisfies the condition';
let found = false;
let result: T = null;
if (
iterable instanceof Array &&
iterable.length == 1 &&
(typeof predicate == 'undefined' || predicate(iterable[0]))
) {
return iterable[0];
} else if (
iterable instanceof SimpleList &&
iterable.count == 1 &&
(typeof predicate == 'undefined' || predicate(iterable.getItem(0)))
) {
return iterable.getItem(0);
} else {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
while (!next.done) {
if (typeof predicate == 'undefined' || predicate(next.value)) {
if (found) {
throw new Error(errNotSingle);
}
result = next.value;
found = true;
}
next = iterator.next();
}
}
if (!found) {
throw new Error(errNoElements);
}
return result;
}
class ConcatIterator<T> implements Iterator<T> {
private firstFinished: boolean;
private iterator: Iterator<T>;
constructor(private iterable1: Iterable<T>, private iterable2: Iterable<T>) {
this.iterator = iterable1[Symbol.iterator]();
}
next(value?: any): IteratorResult<T> {
let result: IteratorResult<T> = null;
if (!this.firstFinished) {
result = this.iterator.next();
if (!result.done) {
return result;
}
this.firstFinished = true;
this.iterator = this.iterable2[Symbol.iterator]();
}
return this.iterator.next();
}
}
export function iuConcat<T>(
first: Iterable<T>,
second: Iterable<T>
): Iterable<T> {
if (first == null || second == null) {
throw new Error('ArgumentNullException');
}
return {
[Symbol.iterator]() {
return new ConcatIterator<T>(first, second);
},
};
}
/**
* Returns whether the iterable contains a specified element.
*
* @param iterable - The iterable source.
* @param value - The element to find.
*/
export function iuContains<T>(
iterable: Iterable<T>,
value: T,
comparer?: any
): boolean {
if (iterable == null) {
throw new Error('ArgumentNullException');
}
// Use 'Equals' to compare elements if available. If not, use '==='.
let isEqual: Function = getCompareFunction<T>(value);
for (const v of iterable) {
if (isEqual(value, v)) {
return true;
}
}
return false;
}
/**
* Base class for DistinctIterator, IntersectIterator and ExceptIterator.
*
* This class contains the logic to detect if the elements have an 'Equals()'
* function, and use it if available to compare elements. If 'Equals()' is
* not available, then '===' is used to compare elements.
*
* The logic to discriminate which elements are returned must be implemented
* on the derived classes.
*/
abstract class BaseDieIterator<T> implements Iterator<T> {
protected baseSet: Set<T>;
protected iterator: Iterator<T>;
protected nextResult: IteratorResult<T, any>;
protected baseSetHas: Function;
protected baseSetDelete: Function;
constructor(first: Iterable<T>, second: Iterable<T>) {
this.iterator = first[Symbol.iterator]();
this.baseSet = new Set(second);
this.baseSetHas = null;
// Get the first element to find out if it has an 'Equals()' function.
this.nextResult = this.iterator.next();
if (!this.nextResult.done) {
if (
this.nextResult?.value &&
typeof this.nextResult.value.Equals === 'function'
) {
// This is slow - O(n): use slow implementations of 'has()'
// and 'delete()' to use 'Equals()' to compare elements.
this.baseSetHas = (value: T): boolean =>
iuContains(this.baseSet, value);
this.baseSetDelete = (value: T): boolean => {
let found = false;
this.baseSet.forEach((x) => {
if (value['Equals'](x)) {
found = true;
this.baseSet.delete(x);
}
});
return found;
};
} else {
// This is fast - O(1): uses the Set directly, but Set does not use 'Equals()'.
// This approach is valid for value types and objects without 'Equals()'.
this.baseSetHas = (value: T): boolean => this.baseSet.has(value);
this.baseSetDelete = (value: T): boolean => this.baseSet.delete(value);
}
}
}
next(_value?: any): IteratorResult<T> {
if (this.nextResult.done) {
return this.nextResult;
}
while (!this.nextResult.done) {
if (this.returnCondition()) {
this.beforeReturn();
const return_result = this.nextResult;
this.nextResult = this.iterator.next();
return return_result;
}
this.nextResult = this.iterator.next();
}
return this.nextResult;
}
abstract returnCondition(): boolean;
abstract beforeReturn(): void;
}
class DistinctIterator<T> extends BaseDieIterator<T> {
constructor(private iterable: Iterable<T>) {
super(iterable, []);
}
returnCondition(): boolean {
return !this.baseSetHas(this.nextResult.value);
}
beforeReturn(): void {
this.baseSet.add(this.nextResult.value);
}
}
function getCompareFunction<T>(value: T): (x: any, y: any) => boolean {
let isEqual: (x: any, y: any) => boolean = null;
if (value && typeof value['Equals'] === 'function') {
isEqual = (left: T, right: T): boolean => left['Equals'](right);
} else {
isEqual = (left: T, right: T): boolean => left === right;
}
return isEqual;
}
/**
* Returns distinct elements from the iterable.
*
* @param iterable - The iterable source.
*/
export function iuDistinct<T>(
iterable: Iterable<T>,
comparer?: any
): Iterable<T> {
if (iterable == null) {
throw new Error('ArgumentNullException');
}
return {
[Symbol.iterator]() {
return new DistinctIterator<T>(iterable);
},
};
}
class IntersectIterator<T> extends BaseDieIterator<T> {
constructor(private first: Iterable<T>, private second: Iterable<T>) {
super(first, second);
}
returnCondition(): boolean {
return this.baseSetHas(this.nextResult.value);
}
beforeReturn(): void {
this.baseSetDelete(this.nextResult.value);
}
}
/**
* Returns the set intersection of two iterables.
*
* @param first
* @param second
*/
export function iuIntersect<T>(
first: Iterable<T>,
second: Iterable<T>,
comparer?: any
): Iterable<T> {
if (first == null || second == null) {
throw new Error('ArgumentNullException');
}
return {
[Symbol.iterator]() {
return new IntersectIterator<T>(first, second);
},
};
}
/**
* Defines a custom {Iterable<T>} object that will iterate thru two
* iterables by ignoring the ones in the second iterable that already exists in the
* first one.
*
* @class UnionIterator
* @implements {Iterable<T>}
* @template T
*/
class UnionIterator<T> implements Iterable<T> {
protected nextResult: IteratorResult<T, any>;
/**
* Creates an instance of UnionIterator for two iterable objects
* @param {Iterable<T>} first the iterable containing the first set of elements
* @param {Iterable<T>} second the iterable containing the second set of elements, the ones
* already in first will be discarded.
* @memberof UnionIterator
*/
constructor(private first: Iterable<T>, private second: Iterable<T>) {}
/**
* Defines the iterator for this class
*
* @return {*} {Iterator<T, any, any>}
* @memberof UnionIterator
*/
*[Symbol.iterator](): Iterator<T, any, any> {
for (const e of this.first) {
yield e;
}
for (const e of this.second) {
/* istanbul ignore else */
if (!iuContains(this.first, e)) {
yield e;
}
}
}
}
/**
* Iterates thru the elements of two iterables by disarding the duplicated
* ones. Default comparison is done using the standard '===' comparison operator
*
* @export
* @template T
* @param {Iterable<T>} first the iterable containing the first set of elements
* @param {Iterable<T>} second the iterable containing the second set of elements, the ones
* already in first will be discarded.
* @param {*} [comparer] The function used to compared elements. Currently not supported.
* @return {*} {Iterable<T>}
*/
export function iuUnion<T>(
first: Iterable<T>,
second: Iterable<T>,
comparer?: any
): Iterable<T> {
if (first == null || second == null) {
throw new Error('ArgumentNullException');
}
return new UnionIterator<T>(first, second);
}
class ExceptIterator<T> extends BaseDieIterator<T> {
constructor(private first: Iterable<T>, private second: Iterable<T>) {
super(first, second);
}
returnCondition(): boolean {
return !this.baseSetHas(this.nextResult.value);
}
beforeReturn(): void {
this.baseSet.add(this.nextResult.value);
}
}
/**
* Returns the set difference of two iterables.
*
* @param first - base iterable.
* @param second - the iterable to substract.
*/
export function iuExcept<T>(
first: Iterable<T>,
second: Iterable<T>
): Iterable<T> {
if (first == null || second == null) {
throw new Error('ArgumentNullException');
}
return {
[Symbol.iterator]() {
return new ExceptIterator<T>(first, second);
},
};
}
export function iuOrderBy<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuOrderBy<T, K>(
iterable: Iterable<T>,
criteria?: (e: T) => K,
comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuOrderBy<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any,
comparer?: IComparer<any>
): IOrderedIterable<T> {
return new IterableForSorting(iterable, criteria, comparer);
}
export function iuOrderByDescending<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuOrderByDescending<T, K>(
iterable: Iterable<T>,
criteria?: (e: T) => K,
comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuOrderByDescending<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any,
comparer?: IComparer<any>
): IOrderedIterable<T> {
return new IterableForSorting(
iterable,
criteria,
new DescendingComparer(comparer)
);
}
class DefaultOrderByComparer implements IComparer<unknown> {
Compare(e1: unknown, e2: unknown): number {
if (typeof e1 == 'number' && typeof e2 == 'number') {
return e1 - e2;
} else if (e1 instanceof Date && e2 instanceof Date) {
return e1.getTime() - e2.getTime();
} else if (typeof e1 == 'string' && typeof e2 == 'string') {
return e1.localeCompare(e2);
} else if (typeof e1 === 'boolean' && typeof e2 === 'boolean') {
return +e1 - +e2;
} else {
// fail with equal
return 0;
}
}
}
class DescendingComparer<T> implements IComparer<T> {
constructor(private comparer: IComparer<T>) {
if (comparer == null) {
this.comparer = new DefaultOrderByComparer();
}
}
Compare(x: T, y: T): number {
return this.comparer.Compare(y, x); // Just reverse elements.
}
}
class IterableForSorting<T> implements IOrderedIterable<T> {
private criterias: ((e: T) => unknown)[] = [];
private comparers: IComparer<unknown>[] = [];
constructor(
private iterable: Iterable<T>,
criteria: (e: T) => unknown,
comparer?: IComparer<unknown>
) {
this.criterias.push(criteria);
this.AddComparer(comparer);
}
CreateOrderedIterable<K>(
keySelector: (e: T) => K,
comparer: IComparer<K>,
descending: boolean
): IOrderedIterable<T> {
Debugger.Throw('Method not implemented.');
return null;
}
[Symbol.iterator](): Iterator<T, any, undefined> {
// until we find a better approach we will be converting
// the collection to an array
const collection = [...this.iterable];
collection.sort((o1, o2) => {
for (let i = 0; i < this.criterias.length; i++) {
const e1 = this.criterias[i](o1);
const e2 = this.criterias[i](o2);
const result = this.comparers[i].Compare(e1, e2);
if (result !== 0) {
return result;
}
}
return 0;
});
return collection[Symbol.iterator]();
}
ThenBy(criteria: (e: T) => unknown, comparer?: IComparer<unknown>): void {
this.criterias.push(criteria);
this.AddComparer(comparer);
}
private AddComparer(comparer?: IComparer<unknown>): void {
if (comparer == null) {
this.comparers.push(new DefaultOrderByComparer());
} else {
this.comparers.push(comparer);
}
}
}
export function iuThenBy<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuThenBy<T, K>(
iterable: Iterable<T>,
criteria?: (e: T) => K,
comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuThenBy<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any,
comparer?: IComparer<any>
): IOrderedIterable<T> {
(iterable as IterableForSorting<T>).ThenBy(criteria, comparer);
return iterable as IOrderedIterable<T>;
}
export function iuThenByDescending<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuThenByDescending<T, K>(
iterable: Iterable<T>,
criteria?: (e: T) => K,
comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuThenByDescending<T>(
iterable: Iterable<T>,
criteria?: (e: T) => any,
comparer?: IComparer<any>
): IOrderedIterable<T> {
(iterable as IterableForSorting<T>).ThenBy(
criteria,
new DescendingComparer(comparer)
);
return iterable as IOrderedIterable<T>;
}
export interface IGrouping<T, K> extends Iterable<T> {
Key: K;
}
class GroupByGrouping<T, K> extends SimpleList<T> implements IGrouping<T, K> {
public Key: K;
}
export function iuGroupBy<T, K>(
iterable: Iterable<T>,
keySelector: (e: T) => K
): Iterable<IGrouping<T, K>>;
export function iuGroupBy<T, K, V>(
iterable: Iterable<T>,
keySelector: (e: T) => K,
valueSelector: (e: T) => V
): Iterable<IGrouping<V, K>>;
export function iuGroupBy<T, K, V>(
iterable: Iterable<T>,
keySelector: (e: T) => K,
valueSelector?: (e: T) => V
): any {
const groups = new Map<string, GroupByGrouping<T | V, K>>();
for (const value of iterable) {
const rawKey = keySelector(value);
const key = (rawKey ?? '').toString();
let group: GroupByGrouping<T | V, K> = null;
if (groups.has(key)) {
group = groups.get(key);
} else {
group = new GroupByGrouping<T, K>();
groups.set(key, group);
group.Key = rawKey;
}
group.add(valueSelector ? valueSelector(value) : value);
}
return groups.values();
}
/**
* Gets a single element from the given iterable. If no predicate then the only element in iterable is returned,
* if more than one element then null is returned. If predicate has value then the only element matching the
* predicate is returned, if more than one matches or none then null is returned.
*
* @param iterable The {@link Iterable<T>} from where to extract the single element
* @param predicate a the predicate to apply to get the single element. If not predicate then the
* iterable value must contain exactly one element.
* @returns
*/
export function iuSingleOrDefault<T>(
iterable: Iterable<T>,
predicate?: (e: T) => boolean
) {
// no predicate, a single element iterable.
if (typeof predicate == 'undefined') {
const iterator = iterable[Symbol.iterator]();
const next = iterator.next();
if (!next.done) {
const result: T = next.value;
if (iterator.next().done) {
// checks for one element only
return result;
}
}
} else {
let matchingElement: T = null;
for (const e of iterable) {
const isMatch: boolean = predicate(e);
if (isMatch) {
if (matchingElement == null) {
matchingElement = e;
} else {
// more than one match returns null
throw new InvalidOperationException(
'more than one element in singleorDefault'
);
}
}
}
if (matchingElement != null) {
return matchingElement;
}
}
return null;
}
class FlattenIteratorWrapper<T, K, V> implements Iterator<V> {
private currentIterator: Iterator<V>;
constructor(
private mainIterator: Iterator<T>,
private func: (e: T) => Iterable<K>,
private resultSelector: (e: T, f: K) => V
) {}
next(value?: any): IteratorResult<V, any> {
if (this.currentIterator) {
const result = this.currentIterator.next();
if (!result.done) {
return result;
}
}
const nextInMain = this.mainIterator.next();
if (!nextInMain.done) {
this.currentIterator = iuSelect(
(innerSingleResult) =>
this.resultSelector(nextInMain.value, innerSingleResult),
this.func(nextInMain.value)
)[Symbol.iterator]();
// controversial recurisive call:
return this.next();
} else {
return { value: undefined, done: true };
}
}
}
export function iuSelectMany<T, K, V>(
func: (e: T) => Iterable<K>,
resultSelector: (e: T, f: K) => V,
iterable: Iterable<T>
): Iterable<V>;
export function iuSelectMany<T, K>(
func: (e: T) => Iterable<K>,
iterable: Iterable<T>
): Iterable<K>;
export function iuSelectMany<T, K, V>(
p1: unknown,
p2: unknown,
p3?: unknown
): unknown {
if (typeof p3 !== 'undefined') {
const iterable = p3 as Iterable<T>;
const func = p1 as (e: T) => Iterable<K>;
const resultSelector = p2 as (e: T, f: K) => V;
return {
[Symbol.iterator]() {
return new FlattenIteratorWrapper(
iterable[Symbol.iterator](),
func,
resultSelector
);
},
};
} else {
const iterable = p2 as Iterable<T>;
const func = p1 as (e: T) => Iterable<K>;
return {
[Symbol.iterator]() {
return new FlattenIteratorWrapper(
iterable[Symbol.iterator](),
func,
(e, f) => f
);
},
};
}
}
class RangeIterator implements Iterator<number> {
constructor(private current: number, private count: number) {}
next(value?: any): IteratorResult<number, any> {
if (this.count > 0) {
this.count--;
return { value: this.current++, done: false };
} else {
return { value: undefined, done: true };
}
}
}
export function iuRange(start: number, count: number): Iterable<number> {
return {
[Symbol.iterator]() {
return new RangeIterator(start, count);
},
};
}
const emptyIterable = {
[Symbol.iterator]() {
return { next: () => ({ done: true, value: undefined }) };
},
};
export function iuEmpty<T>(): Iterable<T> {
return emptyIterable;
}
export function iuAsEnumerable<T>(iterable: Iterable<T>): Iterable<T> {
return iterable;
}
export function iuCast<T>(iterable: Iterable<unknown>): Iterable<T> {
// The following code needs more work to avoid wrong conversions
// In typescript we will not get cast exceptions
return iuSelect((x) => x as T, iterable);
}
export function wrapArrayWithList<T>(arr: T[]): IList<T> {
const result = new SimpleList<T>();
result.internalArray = arr;
return result;
}
export function iuMax<T>(iterable: Iterable<T>): T;
export function iuMax<T, K>(
iterable: Iterable<T>,
valueSelector: (item: T) => K
): K;
export function iuMax<T, K>(
iterable: Iterable<T>,
valueSelector?: (item: T) => K
): K {
let max: K = null;
for (const item of iterable) {
const value = valueSelector ? valueSelector(item) : (item as unknown as K);
if (max === null) {
max = value;
}
if (value > max) {
max = value;
}
}
return max;
}
export function iuMin<T>(
iterable: Iterable<T>,
valueSelector?: (item: T) => number
) {
let min = Number.MAX_VALUE;
for (const item of iterable) {
const value = valueSelector
? valueSelector(item)
: (item as unknown as number);
if (value < min) {
min = value;
}
}
return min;
}
export function iuSum<T>(
iterable: Iterable<T>,
valueSelector: (item: T) => number
) {
let result = 0;
for (const item of iterable) {
const value = valueSelector(item);
result += value;
}
return result;
}
export function iuOfType<K>(
iterable: Iterable<unknown>,
type: any
): Iterable<K> {
// notices that runtime verification of interface types checking is still pending
return iuWhere(
(obj) => obj instanceof type,
iterable
) as unknown as Iterable<K>;
}
export function iuGetUntypedIterator<T>(iterable: Iterable<T>): any {
Debugger.Throw('Not implemented');
}
function* joinGenerator<TInner, TOuter, TKey, TResult>(
iterable: Iterable<TInner>,
iterable2: Iterable<TOuter>,
innerSelector: (inner: TInner) => TKey,
outerSelector: (outer: TOuter) => TKey,
resultSelector: (inner: TInner, outer: TOuter) => TResult
) {
for (const x of iterable) {
const x_key = innerSelector(x);
for (const y of iterable2) {
const y_key = outerSelector(y);
const compare = getCompareFunction(x_key);
if (compare(x_key, y_key)) {
yield resultSelector(x, y);
}
}
}
}
export function iuJoin<TInner, TOuter, TKey, TResult>(
iterable: Iterable<TInner>,
iterable2: Iterable<TOuter>,
innerSelector: (inner: TInner) => TKey,
outerSelector: (outer: TOuter) => TKey,
resultSelector: (inner: TInner, outer: TOuter) => TResult
): Iterable<TResult> {
return {
[Symbol.iterator]() {
return joinGenerator(
iterable,
iterable2,
innerSelector,
outerSelector,
resultSelector
);
},
};
}
/**
* Apply an operation to each element of an Iterable
*
* @export
* @template T
* @param {Iterable<T>} iterable iterable to process
* @param {(e: T, i? : number) => void} func opeartion to apply
*/
export function iuForEach<T>(
iterable: Iterable<T>,
func: (e: T, i?: number) => void
): void {
for (const element of iterable) {
func(element);
}
}
/**
* Adds the contents of an iterable to the given collection
*
* @export
* @template T
* @param {ObjectModelCollection<T>} collection to modified
* @param {Iterable<T>} newElements to add
*/
export function iuAddRange<T>(
collection: ObjectModelCollection<T>,
newElements: Iterable<T>
) {
for (const obj of newElements) {
collection.add(obj);
}
}