org.opentcs.util.UniqueStringGenerator Maven / Gradle / Ivy
The newest version!
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.util;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Provides a way to acquire unique strings.
*
* @param The type of the selectors/keys to be used for mapping to name
* patterns.
*/
public class UniqueStringGenerator {
/**
* Configured name patterns.
*/
private final Map namePatterns = new HashMap<>();
/**
* All strings known to this generator, sorted lexicographically.
*/
private final SortedSet existingStrings = new TreeSet<>();
/**
* Creates a new instance without any name patterns.
*/
public UniqueStringGenerator() {
// Do nada.
}
/**
* Registers a name pattern for the given selector.
*
* @param selector The selector.
* @param prefix The prefix of names to be used for the given selector.
* @param suffixPattern The suffix pattern to be used for the given selector.
*/
public void registerNamePattern(
S selector,
String prefix,
String suffixPattern
) {
namePatterns.put(selector, new NamePattern(prefix, suffixPattern));
}
/**
* Adds a String to those known by this generator. As a result, this generator
* will never return a String that is equal to the given one.
*
* @param newString The string to be added.
*/
public void addString(final String newString) {
requireNonNull(newString, "newString is null");
existingStrings.add(newString);
}
/**
* Makes this generator forget a known String. As a result, this generator
* might return a String that is equal to the given one in the future.
*
* @param rmString The string to be forgotten.
*/
public void removeString(final String rmString) {
requireNonNull(rmString, "rmString is null");
existingStrings.remove(rmString);
}
/**
* Returns true if this generator has this string.
*
* @param str The string to test.
* @return true if this generator already has this string.
*/
public boolean hasString(String str) {
return existingStrings.contains(str);
}
/**
* Removes all known Strings.
*/
public void clear() {
existingStrings.clear();
}
/**
* Returns a string that is unique among all strings registered with this
* generator.
*
* @param selector A selector for the name pattern to be used.
* @return A string that is unique among all strings registered with this
* generator.
*/
public String getUniqueString(S selector) {
requireNonNull(selector, "selector");
NamePattern namePattern = namePatterns.get(selector);
checkArgument(namePattern != null, "Unknown selector: %s", selector);
return getUniqueString(namePattern.prefix, namePattern.suffixPattern);
}
/**
* Returns a String that is unique among all known Strings in this generator.
* The returned String will consist of the given prefix followed by an integer
* formatted according to the given pattern. The pattern has to be of the form
* understood by java.text.DecimalFormat
.
*
* @param prefix The prefix of the String to be generated.
* @param suffixPattern A pattern describing the suffix of the generated
* String. Must be of the form understood by
* java.text.DecimalFormat
.
* @return A String that is unique among all known Strings.
*/
public String getUniqueString(
final String prefix,
final String suffixPattern
) {
requireNonNull(suffixPattern, "suffixPattern is null");
final String actualPrefix = prefix == null ? "" : prefix;
final DecimalFormat format = new DecimalFormat(suffixPattern);
final String lBound = actualPrefix + "0";
final String uBound = actualPrefix + ":";
final int prefixLength = actualPrefix.length();
long maxSuffixValue = 0;
// Get all existing strings with the same prefix and at least one digit
// following it.
for (String curName : existingStrings.subSet(lBound, uBound)) {
// Check if the suffix contains only digits.
boolean allDigits = containsOnlyDigits(curName.substring(prefixLength));
// If the suffix contains only digits, parse it and remember the maximum
// suffix value we found so far. (If the suffix contains other characters,
// ignore this string - we generate suffixes with digits only, so there
// can't be a collision.
if (allDigits) {
final long curSuffixValue = NumberParsers.parsePureDecimalLong(
curName, prefixLength, curName.length() - prefixLength
);
maxSuffixValue
= maxSuffixValue > curSuffixValue ? maxSuffixValue : curSuffixValue;
}
}
// Increment the highest value found and use that as the suffix
return actualPrefix + format.format(maxSuffixValue + 1);
}
/**
* Checks if the given string contains only (decimal) digits.
*
* @param input The string to be checked.
* @return true
if, and only if, the given string contains only
* (decimal) digits.
*/
private boolean containsOnlyDigits(String input) {
assert input != null;
for (int i = 0; i < input.length(); i++) {
int digit = input.charAt(i) - '0';
if (digit < 0 || digit > 9) {
return false;
}
}
return true;
}
/**
* A name pattern.
*/
private static class NamePattern {
/**
* The prefix to be used.
*/
private final String prefix;
/**
* The suffix pattern to be used.
*/
private final String suffixPattern;
/**
* Creates a new instance.
*
* @param prefix The prefix to be used.
* @param suffixPattern The suffix pattern to be used.
*/
private NamePattern(String prefix, String suffixPattern) {
this.prefix = requireNonNull(prefix, "prefix");
this.suffixPattern = requireNonNull(suffixPattern, "suffixPattern");
}
}
}