/**
 * Holds state on already used names and provides new names if there are naming conflicts.
 */ export class UniqueNameGenerator { private static readonly MAXIMUM_NUMBER_OF_SUFFIX = 1000 ; private static getNameForComparison ( name , caseSensitive ) { return caseSensitive ? name : name . toLowerCase ( ) ; } private usedNames : string [ ] = [ ] ; param indexSeparator param usedNames public constructor ( private indexSeparator = '_' , usedNames = [ ] ) { this . addToUsedNames ( ... usedNames ) ; } param names public addToUsedNames ( ... names ) { this . usedNames . push ( ... names ) ; } param name param caseSensitive returns public generateUniqueName ( name , caseSensitive = true ) { return this . generateUniqueNamesWithSuffixes ( name , [ ] , caseSensitive ) [ 0 ] ; } param name param caseSensitive returns public generateAndSaveUniqueName ( name , caseSensitive = true ) { const uniqueName = this . generateUniqueName ( name , caseSensitive ) ; this . addToUsedNames ( uniqueName ) ; return uniqueName ; } param name param suffixes param caseSensitive returns public generateUniqueNamesWithSuffixes ( name , suffixes , caseSensitive = true ) { const relevantUsedNames = this . getUsedNamesStartingWith ( name , caseSensitive ) ; const namesWithSuffixes = this . generateNamesWithSuffixes ( name , suffixes ) ; if ( ! this . areNamesUsed ( namesWithSuffixes , relevantUsedNames , caseSensitive ) ) { return [ name , ... namesWithSuffixes ] ; } const index = this . getUniqueIndex ( name , relevantUsedNames , suffixes , caseSensitive ) ; return this . generateNamesWithIndexAndSuffixes ( name , index , suffixes ) ; } param name param suffixes param caseSensitive returns public generateAndSaveUniqueNamesWithSuffixes ( name , suffixes , caseSensitive = true ) { const uniqueNames = this . generateUniqueNamesWithSuffixes ( name , suffixes , caseSensitive ) ; this . addToUsedNames ( ... uniqueNames ) ; return uniqueNames ; } private getUsedNamesForComparison ( caseSensitive ) { return this . usedNames . map ( name => UniqueNameGenerator . getNameForComparison ( name , caseSensitive ) ) ; } private areNamesUsed ( names , usedNames , caseSensitive ) { return names . some ( name => usedNames . map ( usedName => UniqueNameGenerator . getNameForComparison ( usedName , caseSensitive ) ) . includes ( UniqueNameGenerator . getNameForComparison ( name , caseSensitive ) ) ) ; } private generateNamesWithIndexAndSuffixes ( name , index , suffixes ) { const nameWithoutIndex = this . getNameWithoutIndex ( name ) ; return this . generateNamesWithSuffixes ( `${ nameWithoutIndex }${ this . indexSeparator }${ index }` , suffixes ) ; } private generateNamesWithSuffixes ( name , suffixes ) { return [ name , ... suffixes . map ( nameSuffix => `${ name }${ nameSuffix }` ) ] ; } private getUsedNamesStartingWith ( name , caseSensitive ) { const modifiedName = this . getNameWithoutIndex ( name ) ; return this . getUsedNamesForComparison ( caseSensitive ) . filter ( used => used . startsWith ( UniqueNameGenerator . getNameForComparison ( modifiedName , caseSensitive ) ) ) ; } private getUniqueIndex ( name , usedNames , suffixes , caseSensitive ) { let index = 1 ; while ( index < UniqueNameGenerator . MAXIMUM_NUMBER_OF_SUFFIX ) { const newNames = this . generateNamesWithIndexAndSuffixes ( name , index , suffixes ) ; if ( ! this . areNamesUsed ( newNames , usedNames , caseSensitive ) ) { return index ; } index ++ ; } throw new Error ( `Unable to find a unique name for ${ name } within the range of ${ UniqueNameGenerator . MAXIMUM_NUMBER_OF_SUFFIX } suffixes.` ) ; } private getNameWithoutIndex ( name ) { return name . replace ( new RegExp ( `${ this . indexSeparator }\\d+$` ) , '' ) ; } }