All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.RandomNameGenerator Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2015 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Chars;
import com.google.javascript.rhino.TokenStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * A class for generating unique, randomized JavaScript variable/property
 * names.
 *
 * 

Unlike NameGenerator, names do not follow a predictable sequence such as * a, b, ... z, aa, ab, ..., az, ba, ... * but instead they are random, based on an external random seed. We do * partially compromise for efficiency in that *

    *
  • Generated names will have the same length as they would with * NameGenerator *
  • We don't use a completely different alphabet for each name prefix, but * instead choose among a few with a predictable formula. *
* *

More precisely: *

    *
  • We compute a random shuffle of the alphabet for "first characters", and * a small number of random shuffles of the alphabet for "non-first * characters". Then we do a typical number-to-text conversion of a name's * "index", where the alphabet for digits is not just 0 to 9. The least * significant digit comes first. *
  • We represent each digit using an appropriate alphabet. If it's not the * first character of the name (i.e. not the least significant one, or there * is a constant prefix) then we have several appropriate alphabets to choose * from; we choose one based a hash of the previous digits of this name. *
* *

This class is not thread safe. */ @GwtIncompatible( "java.util.Collections.shuffle, " + "com.google.common.hash.Hasher, " + "com.google.common.hash.Hashing") public final class RandomNameGenerator implements NameGenerator { /** Generate random names with this first character. */ static final ImmutableSet FIRST_CHAR = asSet(DefaultNameGenerator.FIRST_CHAR); /** These appear after after the first character */ static final ImmutableSet NONFIRST_CHAR = asSet(DefaultNameGenerator.NONFIRST_CHAR); /** The possible first characters, after reserved characters are removed */ private ImmutableSet firstChars; /** Possible non-first characters, after reserved characters are removed */ private ImmutableSet nonFirstChars; /** Source of randomness */ private final Random random; /** List of reserved names; these are not returned by generateNextName */ private ImmutableSet reservedNames; /** Prefix added to all generated names */ private String prefix; /** How many names have we issued so far (includes names that cannot be used * because they are reserved through 'reservedNames' or JavaScript * keywords) */ private int nameCount; /** How many shuffles of nonFirstChars to generate */ private static final int NUM_SHUFFLES = 16; /** Randomly-shuffled version of firstChars */ private String shuffledFirst; /** Randomly-shuffled versions of nonFirstChars (there are NUM_SHUFFLES of them) */ private ImmutableList shuffledNonFirst; public RandomNameGenerator(Random random) { this.random = random; reset(ImmutableSet.of(), "", null); } RandomNameGenerator( Set reservedNames, String prefix, @Nullable char[] reservedCharacters, Random random) { this.random = random; reset(reservedNames, prefix, reservedCharacters); } /** * Creates a RandomNameGenerator. * * @param reservedNames set of names that are reserved; generated names will not include these * names. This set is referenced rather than copied, so changes to the set will be reflected * in how names are generated * @param prefix all generated names begin with this prefix (a name consisting of only this * prefix, with no suffix, will not be generated) * @param reservedFirstCharacters if specified these characters won't be used in generated names * for the first character * @param reservedNonFirstCharacters if specified these characters won't be used in generated * names for characters after the first * @param random source of randomness when generating random names */ RandomNameGenerator( Set reservedNames, String prefix, @Nullable char[] reservedFirstCharacters, @Nullable char[] reservedNonFirstCharacters, Random random) { this.random = random; reset(reservedNames, prefix, reservedFirstCharacters, reservedNonFirstCharacters); } @Override public void reset( Set reservedNames, String prefix, @Nullable char[] reservedCharacters) { reset(reservedNames, prefix, reservedCharacters, reservedCharacters); } @Override public void reset( Set reservedNames, String prefix, @Nullable char[] reservedFirstCharacters, @Nullable char[] reservedNonFirstCharacters) { this.reservedNames = ImmutableSet.copyOf(reservedNames); this.prefix = prefix; nameCount = 0; // Build the character arrays to use firstChars = Sets.difference(FIRST_CHAR, asSet(reservedFirstCharacters)).immutableCopy(); nonFirstChars = Sets.difference(NONFIRST_CHAR, asSet(reservedNonFirstCharacters)).immutableCopy(); checkPrefix(prefix); shuffleAlphabets(); } @Override public NameGenerator clone( Set reservedNames, String prefix, @Nullable char[] reservedCharacters) { return new RandomNameGenerator( reservedNames, prefix, reservedCharacters, random); } private static ImmutableSet asSet(@Nullable char[] chars) { return chars == null ? ImmutableSet.of() : ImmutableSet.copyOf(Chars.asList(chars)); } /** * Validates a name prefix. */ private void checkPrefix(String prefix) { if (prefix.length() > 0) { // Make sure that prefix starts with a legal character. if (!firstChars.contains(prefix.charAt(0))) { throw new IllegalArgumentException( "prefix must start with one of: " + Joiner.on(", ").join(firstChars)); } for (int pos = 1; pos < prefix.length(); ++pos) { if (!nonFirstChars.contains(prefix.charAt(pos))) { throw new IllegalArgumentException( "prefix has invalid characters, must be one of: " + Joiner.on(", ").join(nonFirstChars)); } } } } private static String shuffleAndCopyAlphabet(Set input, Random random) { List shuffled = new ArrayList<>(input); Collections.shuffle(shuffled, random); return new String(Chars.toArray(shuffled)); } /** Generates random shuffles of the alphabets. */ private void shuffleAlphabets() { shuffledFirst = shuffleAndCopyAlphabet(firstChars, random); ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < NUM_SHUFFLES; ++i) { builder.add(shuffleAndCopyAlphabet(nonFirstChars, random)); } shuffledNonFirst = builder.build(); } /** * Computes the length (in digits) for a name suffix. */ private int getNameLength(int position, int nameIdx) { int length = 0; nameIdx++; do { nameIdx--; int alphabetSize = position == 0 ? firstChars.size() : nonFirstChars.size(); nameIdx /= alphabetSize; position++; length++; } while (nameIdx > 0); return length; } /** * Returns the {@code nameIdx}-th short name. This might be a reserved name. * A user-requested prefix is not included, but the first returned character * is supposed to go at position {@code position} in the final name */ private String generateSuffix(int position, int nameIdx) { StringBuilder name = new StringBuilder(); int length = getNameLength(position, nameIdx); nameIdx++; do { nameIdx--; String alphabet; if (position == 0) { alphabet = shuffledFirst; } else { Hasher hasher = Hashing.murmur3_128().newHasher(); hasher.putInt(length); hasher.putUnencodedChars(name); int alphabetIdx = (hasher.hash().asInt() & 0x7fffffff) % NUM_SHUFFLES; alphabet = shuffledNonFirst.get(alphabetIdx); } int alphabetSize = alphabet.length(); char character = alphabet.charAt(nameIdx % alphabetSize); name.append(character); nameIdx /= alphabetSize; position++; } while (nameIdx > 0); return name.toString(); } /** * Generates the next short name. * *

This generates names of increasing length. To minimize output size, * therefore, it's good to call it for the most used symbols first. */ @Override public String generateNextName() { while (true) { String name = prefix + generateSuffix(prefix.length(), nameCount++); // Make sure it's not a JS keyword or reserved name. if (TokenStream.isKeyword(name) || reservedNames.contains(name)) { continue; } return name; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy