/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.template.soy.base.internal;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.CharMatcher;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import java.util.HashSet;
import java.util.Set;
/**
* Manages a set of unique names within a given context, and provides helper methods for generating
* unique names from other names, which may or may not be sufficiently unique on their own.
*/
public final class UniqueNameGenerator {
private final Set<String> reserved = new HashSet<>();
private final Multiset<String> names = HashMultiset.create();
private final CharMatcher bannedCharacters;
private final String collisionSeparator;
public UniqueNameGenerator(CharMatcher bannedCharacters, String collisionSeparator) {
checkArgument(
bannedCharacters.matchesNoneOf(collisionSeparator),
"separator %s contains banned characters",
collisionSeparator);
this.bannedCharacters = bannedCharacters;
this.collisionSeparator = collisionSeparator;
}
/** Registers the name, throwing an IllegalArgumentException if it has already been registered. */
public void claimName(String name) {
checkName(name);
if (names.add(name, 1) != 0) {
names.remove(name);
// give a slightly better error message in this case
if (reserved.contains(name)) {
throw new IllegalArgumentException("Tried to claim a reserved name: " + name);
}
throw new IllegalArgumentException("Name: " + name + " was already claimed!");
}
}
/** Reserves the names, useful for keywords. */
public void reserve(Iterable<String> names) {
for (String name : names) {
reserve(name);
}
}
/** Reserves the name, useful for keywords. */
public void reserve(String name) {
checkName(name);
// if this is new
if (reserved.add(name)) {
// add it to names, so that generateName will still work for reserved names (they will just
// get suffixes).
if (!names.add(name)) {
names.remove(name);
throw new IllegalArgumentException("newly reserved name: " + name + " was already used!");
}
}
}
/**
* Returns a name based on the supplied one that is guaranteed to be unique among the names that
* have been returned by this method.
*/
public String generateName(String name) {
checkName(name);
names.add(name);
int count = names.count(name);
if (count == 1) {
return name;
}
return name + collisionSeparator + (count - 1);
}
public boolean hasName(String name) {
int separator = name.lastIndexOf(collisionSeparator);
return names.contains(separator == -1 ? name : name.substring(0, separator));
}
private void checkName(String name) {
checkArgument(!name.isEmpty());
checkArgument(
!name.contains(collisionSeparator),
"%s contains the separation character: '%s'",
name,
collisionSeparator);
checkArgument(!bannedCharacters.matchesAnyOf(name), "%s contains dangerous characters!", name);
}
}