Files
create/src/Base/UniqueNameManager.cpp
2025-11-11 13:49:01 +01:00

185 lines
7.8 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2024 Kevin Martin <kpmartin@papertrail.ca> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <algorithm>
#include <tuple>
#include <vector>
#include <string>
#include <set>
#include "UniqueNameManager.h"
std::tuple<std::string, std::string, unsigned int, Base::UnlimitedUnsigned> Base::UniqueNameManager::decomposeName(
const std::string& name
) const
{
auto suffixStart = getNameSuffixStartPosition(name);
auto digitsStart = std::find_if_not(suffixStart, name.crend(), [](char c) {
return std::isdigit(c);
});
unsigned int digitCount = digitsStart - suffixStart;
return std::tuple<std::string, std::string, unsigned int, UnlimitedUnsigned> {
name.substr(0, name.crend() - digitsStart),
name.substr(name.crend() - suffixStart),
digitCount,
UnlimitedUnsigned::fromString(name.substr(name.crend() - digitsStart, digitCount))
};
}
bool Base::UniqueNameManager::haveSameBaseName(const std::string& first, const std::string& second) const
{
auto firstSuffixStart = getNameSuffixStartPosition(first);
auto secondSuffixStart = getNameSuffixStartPosition(second);
if (firstSuffixStart - first.crbegin() != secondSuffixStart - second.crbegin()) {
// The suffixes are different lengths
return false;
}
auto firstDigitsStart = std::find_if_not(firstSuffixStart, first.crend(), [](char c) {
return std::isdigit(c);
});
auto secondDigitsStart = std::find_if_not(secondSuffixStart, second.crend(), [](char c) {
return std::isdigit(c);
});
return std::equal(firstDigitsStart, first.crend(), secondDigitsStart, second.crend());
}
void Base::UniqueNameManager::addExactName(const std::string& name)
{
auto [baseName, nameSuffix, digitCount, digitsValue] = decomposeName(name);
baseName += nameSuffix;
auto baseNameEntry = uniqueSeeds.find(baseName);
if (baseNameEntry == uniqueSeeds.end()) {
// First use of baseName
baseNameEntry
= uniqueSeeds
.emplace(baseName, std::vector<PiecewiseSparseIntegerSet<UnlimitedUnsigned>>())
.first;
}
if (digitCount >= baseNameEntry->second.size()) {
// First use of this digitCount
baseNameEntry->second.resize(digitCount + 1);
}
PiecewiseSparseIntegerSet<UnlimitedUnsigned>& baseNameAndDigitCountEntry
= baseNameEntry->second[digitCount];
if (baseNameAndDigitCountEntry.contains(digitsValue)) {
// We already have at least one instance of the name.
// Increment the duplicateCounts entry for that name,
// making one if none is present with an initial count of 1
// representing the singleton element we already have.
duplicateCounts.try_emplace(name, 1U).first->second++;
return;
}
baseNameAndDigitCountEntry.add(digitsValue);
}
std::string Base::UniqueNameManager::makeUniqueName(
const std::string& modelName,
std::size_t minDigits
) const
{
auto [namePrefix, nameSuffix, digitCount, digitsValue] = decomposeName(modelName);
std::string baseName = namePrefix + nameSuffix;
auto baseNameEntry = uniqueSeeds.find(baseName);
if (baseNameEntry == uniqueSeeds.end()) {
// First use of baseName, just return it with no unique digits
return baseName;
}
// We don't care about the digit count or value of the suggested name,
// we always use at least the most digits ever used before.
digitCount = baseNameEntry->second.size() - 1;
if (digitCount < minDigits) {
// Caller is asking for more digits than we have in any registered name.
// We start the longer digit string at 000...0001 even though we might have shorter strings
// with larger numeric values.
digitCount = minDigits;
digitsValue = UnlimitedUnsigned(1);
}
else {
digitsValue = baseNameEntry->second[digitCount].next();
}
std::string digits = digitsValue.toString();
if (digitCount > digits.size()) {
namePrefix += std::string(digitCount - digits.size(), '0');
}
return namePrefix + digits + nameSuffix;
}
void Base::UniqueNameManager::removeExactName(const std::string& name)
{
auto duplicateCountFound = duplicateCounts.find(name);
if (duplicateCountFound != duplicateCounts.end()) {
// The name has duplicates. Decrement the duplicate count.
if (--duplicateCountFound->second <= 1) {
// After removal, there are no duplicates, only a single instance.
// Remove the duplicateCounts entry.
duplicateCounts.erase(duplicateCountFound);
}
return;
}
auto [baseName, nameSuffix, digitCount, digitsValue] = decomposeName(name);
baseName += nameSuffix;
auto baseNameEntry = uniqueSeeds.find(baseName);
if (baseNameEntry == uniqueSeeds.end()) {
// name must not be registered, so nothing to do.
return;
}
auto& digitValueSets = baseNameEntry->second;
if (digitCount >= digitValueSets.size()) {
// First use of this digitCount, name must not be registered, so nothing to do.
return;
}
digitValueSets[digitCount].remove(digitsValue);
// an element of digitValueSets may now be newly empty and so may other elements below it
// Prune off all such trailing empty entries.
auto lastNonemptyEntry
= std::find_if(digitValueSets.crbegin(), digitValueSets.crend(), [](auto& it) {
return !it.empty();
});
if (lastNonemptyEntry == digitValueSets.crend()) {
// All entries are empty, so the entire baseName can be forgotten.
uniqueSeeds.erase(baseName);
}
else {
digitValueSets.resize(digitValueSets.crend() - lastNonemptyEntry);
}
}
bool Base::UniqueNameManager::containsName(const std::string& name) const
{
if (duplicateCounts.find(name) != duplicateCounts.end()) {
// There are at least two instances of the name
return true;
}
auto [baseName, nameSuffix, digitCount, digitsValue] = decomposeName(name);
baseName += nameSuffix;
auto baseNameEntry = uniqueSeeds.find(baseName);
if (baseNameEntry == uniqueSeeds.end()) {
// base name is not registered
return false;
}
if (digitCount >= baseNameEntry->second.size()) {
// First use of this digitCount, name must not be registered, so not in collection
return false;
}
return baseNameEntry->second[digitCount].contains(digitsValue);
}