Skip to content

Commit

Permalink
Upgrade and improve Uniquifier API (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkorbel1 authored Aug 22, 2024
1 parent 9910774 commit 3e2d24a
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 28 deletions.
51 changes: 24 additions & 27 deletions lib/src/utilities/uniquifier.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2023 Intel Corporation
// Copyright (C) 2021-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// uniquifier.dart
Expand Down Expand Up @@ -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;
}
}
68 changes: 67 additions & 1 deletion test/uniquifier_test.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 Intel Corporation
// Copyright (C) 2023-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// uniquifier_test.dart
Expand All @@ -19,4 +19,70 @@ void main() {
expect(() => uniq.getUniqueName(initialName: 'apple', reserved: true),
throwsA(isA<UnavailableReservedNameException>()));
});

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);
});
});
}

0 comments on commit 3e2d24a

Please sign in to comment.