diff --git a/lib/src/utilities/uniquifier.dart b/lib/src/utilities/uniquifier.dart index f1b877b2d..86c875e21 100644 --- a/lib/src/utilities/uniquifier.dart +++ b/lib/src/utilities/uniquifier.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // uniquifier.dart @@ -37,56 +37,53 @@ class Uniquifier { : _reservedNames = reservedNames ?? {}; /// Returns `true` iff [name] is exactly available without uniquification. - bool isAvailable(String name) => !_reservedNames.contains(name); + /// + /// If [reserved] is set to `true`, then it will return that the [name] is + /// available if it is reserved but not yet taken. + bool isAvailable(String name, {bool reserved = false}) => + !_takenNames.contains(name) && + (reserved || !_reservedNames.contains(name)); /// Provides a uniquified name that has never been returned by this /// [Uniquifier]. /// /// If it is specified and there is no conflict, it will always choose /// [initialName]. If no [initialName] is specified, it will name it using - /// [nullStarter]. From the starting point, it will increment an integer + /// [nullStarter]. From the starting point, it will increment an integer /// appended to the end until no more conflict exists. /// /// Setting [reserved] will ensure the name does not get modified from its - /// original name. If a reserved name is already taken, an exception - /// will be thrown. + /// original name. If a reserved name is already taken, an exception will be + /// thrown. String getUniqueName( {String? initialName, bool reserved = false, String nullStarter = 'i'}) { + String actualName; + if (reserved) { if (initialName == null) { throw NullReservedNameException(); } else if (initialName.isEmpty) { throw EmptyReservedNameException(); + } else if (!isAvailable(initialName, reserved: reserved)) { + throw UnavailableReservedNameException(initialName); } - } - final requestedName = initialName ?? nullStarter; - var actualName = requestedName; + actualName = initialName; + } else { + final requestedName = initialName ?? nullStarter; - String constructActualName() => - '${requestedName}_${_nameCounters[requestedName]!}'; + actualName = requestedName; - if (!_nameCounters.containsKey(initialName)) { - _nameCounters[requestedName] = -1; // first one should be 0 - } else { - _nameCounters[requestedName] = _nameCounters[requestedName]! + 1; - actualName = constructActualName(); - } - while (_takenNames.contains(actualName) || - (!reserved && reservedNames.contains(actualName))) { - _nameCounters[requestedName] = _nameCounters[requestedName]! + 1; - actualName = constructActualName(); - } + while (!isAvailable(actualName, reserved: reserved)) { + // initialize counter if necessary + _nameCounters[requestedName] ??= -1; // first one should be 0 - if (reserved && initialName != actualName) { - throw UnavailableReservedNameException(initialName!); + _nameCounters[requestedName] = _nameCounters[requestedName]! + 1; + actualName = '${requestedName}_${_nameCounters[requestedName]!}'; + } } _takenNames.add(actualName); - if (reserved) { - _reservedNames.add(actualName); - } - return actualName; } } diff --git a/test/uniquifier_test.dart b/test/uniquifier_test.dart index d20086832..2e0eff487 100644 --- a/test/uniquifier_test.dart +++ b/test/uniquifier_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Intel Corporation +// Copyright (C) 2023-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // uniquifier_test.dart @@ -19,4 +19,70 @@ void main() { expect(() => uniq.getUniqueName(initialName: 'apple', reserved: true), throwsA(isA())); }); + + test('uniquify name if requested twice', () { + final uniq = Uniquifier(); + final name1 = uniq.getUniqueName(initialName: 'apple'); + final name2 = uniq.getUniqueName(initialName: 'apple'); + expect(name1, 'apple'); + expect(name1, isNot(name2)); + }); + + test('uniquify name if reserved', () { + final uniq = Uniquifier(reservedNames: {'apple'}); + final name1 = uniq.getUniqueName(initialName: 'apple'); + final name2 = uniq.getUniqueName(initialName: 'apple', reserved: true); + expect(name1, isNot('apple')); + expect(name2, 'apple'); + expect(name1, isNot(name2)); + }); + + test('uniquify incrementing name', () { + final uniq = Uniquifier(reservedNames: {'apple_4'}); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple'); + expect(uniq.getUniqueName(initialName: 'apple_2'), 'apple_2'); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple_0'); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple_1'); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple_3'); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple_5'); + expect( + uniq.getUniqueName(initialName: 'apple_4', reserved: true), 'apple_4'); + expect(uniq.getUniqueName(initialName: 'apple'), 'apple_6'); + }); + + test('null starter uniquify', () { + final uniq = Uniquifier(); + expect(uniq.getUniqueName(nullStarter: 'a'), 'a'); + expect(uniq.getUniqueName(nullStarter: 'a'), 'a_0'); + expect(uniq.getUniqueName(), 'i'); + expect(uniq.getUniqueName(), 'i_0'); + }); + + group('isAvailable', () { + test('available name', () { + final uniq = Uniquifier(); + expect(uniq.isAvailable('apple'), isTrue); + }); + + test('taken name', () { + final uniq = Uniquifier()..getUniqueName(initialName: 'apple'); + expect(uniq.isAvailable('apple'), isFalse); + }); + + test('reserved name not available', () { + final uniq = Uniquifier(reservedNames: {'apple'}); + expect(uniq.isAvailable('apple'), isFalse); + }); + + test('reserved name available for reserved', () { + final uniq = Uniquifier(reservedNames: {'apple'}); + expect(uniq.isAvailable('apple', reserved: true), isTrue); + }); + + test('already used reserved name unavailable', () { + final uniq = Uniquifier(reservedNames: {'apple'}) + ..getUniqueName(initialName: 'apple', reserved: true); + expect(uniq.isAvailable('apple', reserved: true), isFalse); + }); + }); }