diff --git a/src/domain/Application.ts b/src/domain/Application.ts index f5a54270..ef521315 100644 --- a/src/domain/Application.ts +++ b/src/domain/Application.ts @@ -4,130 +4,89 @@ import { IScript } from './IScript'; import { IApplication } from './IApplication'; export class Application implements IApplication { - private static mustHaveCategories(categories: ReadonlyArray) { - if (!categories || categories.length === 0) { - throw new Error('an application must consist of at least one category'); - } - } + public get totalScripts(): number { return this.flattened.allScripts.length; } + public get totalCategories(): number { return this.flattened.allCategories.length; } - /** - * Checks all categories against duplicates, throws exception if it find any duplicates - * @return {number} Total unique categories - */ - /** Checks all categories against duplicates, throws exception if it find any duplicates returns total categories */ - private static mustNotHaveDuplicatedCategories(categories: ReadonlyArray): number { - return Application.ensureNoDuplicateEntities(categories, Application.visitAllCategoriesOnce); - } - - /** - * Checks all scripts against duplicates, throws exception if it find any scripts duplicates total scripts. - * @return {number} Total unique scripts - */ - private static mustNotHaveDuplicatedScripts(categories: ReadonlyArray): number { - return Application.ensureNoDuplicateEntities(categories, Application.visitAllScriptsOnce); - } - - /** - * Checks entities against duplicates using a visit function, throws exception if it find any duplicates. - * @return {number} Result from the visit function - */ - private static ensureNoDuplicateEntities( - categories: ReadonlyArray, - visitFunction: (categories: ReadonlyArray, - handler: (entity: IEntity) => any) => number): number { - const totalOccurencesById = new Map(); - const totalVisited = visitFunction(categories, - (entity) => - totalOccurencesById.set(entity.id, - (totalOccurencesById.get(entity.id) || 0) + 1)); - const duplicatedIds = new Array(); - totalOccurencesById.forEach((count, id) => { - if (count > 1) { - duplicatedIds.push(id); - } - }); - if (duplicatedIds.length > 0) { - const duplicatedIdsText = duplicatedIds.map((id) => `"${id}"`).join(','); - throw new Error( - `Duplicate entities are detected with following id(s): ${duplicatedIdsText}`); - } - return totalVisited; - } - - // Runs handler on each category and returns sum of total visited categories - private static visitAllCategoriesOnce( - categories: ReadonlyArray, - handler: (category: ICategory) => any): number { - let total = 0; - for (const category of categories) { - handler(category); - total++; - if (category.subCategories && category.subCategories.length > 0) { - total += Application.visitAllCategoriesOnce( - category.subCategories as ReadonlyArray, - handler); - } - } - return total; - } - - // Runs handler on each script and returns sum of total visited scripts - private static visitAllScriptsOnce( - categories: ReadonlyArray, - handler: (script: IScript) => any): number { - let total = 0; - Application.visitAllCategoriesOnce(categories, - (category) => { - if (category.scripts) { - for (const script of category.scripts) { - handler(script); - total++; - } - } - }); - return total; - } - - public readonly totalScripts: number; - public readonly totalCategories: number; + private readonly flattened: IFlattenedApplication; constructor( public readonly name: string, public readonly version: number, public readonly categories: ReadonlyArray) { - Application.mustHaveCategories(categories); - this.totalCategories = Application.mustNotHaveDuplicatedCategories(categories); - this.totalScripts = Application.mustNotHaveDuplicatedScripts(categories); + if (!name) { + throw Error('Application has no name'); + } + if (!version) { + throw Error('Version cannot be zero'); + } + this.flattened = flatten(categories); + if (this.flattened.allCategories.length === 0) { + throw new Error('An application must consist of at least one category'); + } + ensureNoDuplicates(this.flattened.allCategories); + ensureNoDuplicates(this.flattened.allScripts); } public findCategory(categoryId: number): ICategory | undefined { - let result: ICategory | undefined; - Application.visitAllCategoriesOnce(this.categories, - (category) => { - if (category.id === categoryId) { - result = category; - } - }); - return result; + return this.flattened.allCategories.find((category) => category.id === categoryId); } public findScript(scriptId: string): IScript | undefined { - let result: IScript | undefined; - Application.visitAllScriptsOnce(this.categories, - (script) => { - if (script.id === scriptId) { - result = script; - } - }); - return result; + return this.flattened.allScripts.find((script) => script.id === scriptId); } public getAllScripts(): IScript[] { - const result = new Array(); - Application.visitAllScriptsOnce(this.categories, - (script) => { - result.push(script); - }); - return result; + return this.flattened.allScripts; } } + +function ensureNoDuplicates(entities: ReadonlyArray>) { + const totalOccurencesById = new Map(); + for (const entity of entities) { + totalOccurencesById.set(entity.id, (totalOccurencesById.get(entity.id) || 0) + 1); + } + const duplicatedIds = new Array(); + totalOccurencesById.forEach((index, id) => { + if (index > 1) { + duplicatedIds.push(id); + } + }); + if (duplicatedIds.length > 0) { + const duplicatedIdsText = duplicatedIds.map((id) => `"${id}"`).join(','); + throw new Error( + `Duplicate entities are detected with following id(s): ${duplicatedIdsText}`); + } +} + +interface IFlattenedApplication { + allCategories: ICategory[]; + allScripts: IScript[]; +} + +function flattenRecursive( + categories: ReadonlyArray, + flattened: IFlattenedApplication) { + for (const category of categories) { + flattened.allCategories.push(category); + if (category.scripts) { + for (const script of category.scripts) { + flattened.allScripts.push(script); + } + } + if (category.subCategories && category.subCategories.length > 0) { + flattenRecursive( + category.subCategories as ReadonlyArray, + flattened); + } + } +} + +function flatten( + categories: ReadonlyArray): IFlattenedApplication { + const flattened: IFlattenedApplication = { + allCategories: new Array(), + allScripts: new Array(), + }; + flattenRecursive(categories, flattened); + return flattened; +}