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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
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 org.jspecify.nullness.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, char @Nullable [] 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, char @Nullable [] reservedFirstCharacters, char @Nullable [] reservedNonFirstCharacters, Random random) { this.random = random; reset(reservedNames, prefix, reservedFirstCharacters, reservedNonFirstCharacters); } @Override public void reset( Set reservedNames, String prefix, char @Nullable [] reservedCharacters) { reset(reservedNames, prefix, reservedCharacters, reservedCharacters); } @Override public void reset( Set reservedNames, String prefix, char @Nullable [] reservedFirstCharacters, char @Nullable [] 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, char @Nullable [] reservedCharacters) { return new RandomNameGenerator( reservedNames, prefix, reservedCharacters, random); } private static ImmutableSet asSet(char @Nullable [] 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