Skip to content

Commit

Permalink
Change RandomNameGenerator to use Hasher API correctly.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=158293027
  • Loading branch information
herbyderby authored and brad4d committed Jun 8, 2017
1 parent 99441c8 commit 5fb2076
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 83 deletions.
116 changes: 43 additions & 73 deletions src/com/google/javascript/jscomp/RandomNameGenerator.java
Expand Up @@ -18,15 +18,15 @@


import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.Lists; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.hash.Hasher; import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.common.primitives.Chars; import com.google.common.primitives.Chars;
import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.TokenStream;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
Expand Down Expand Up @@ -69,22 +69,22 @@
public final class RandomNameGenerator implements NameGenerator { public final class RandomNameGenerator implements NameGenerator {


/** Generate random names with this first character. */ /** Generate random names with this first character. */
static final char[] FIRST_CHAR = DefaultNameGenerator.FIRST_CHAR; static final ImmutableSet<Character> FIRST_CHAR = asSet(DefaultNameGenerator.FIRST_CHAR);


/** These appear after after the first character */ /** These appear after after the first character */
static final char[] NONFIRST_CHAR = DefaultNameGenerator.NONFIRST_CHAR; static final ImmutableSet<Character> NONFIRST_CHAR = asSet(DefaultNameGenerator.NONFIRST_CHAR);


/** The possible first characters, after reserved characters are removed */ /** The possible first characters, after reserved characters are removed */
private LinkedHashSet<Character> firstChars; private ImmutableSet<Character> firstChars;


/** Possible non-first characters, after reserved characters are removed */ /** Possible non-first characters, after reserved characters are removed */
private LinkedHashSet<Character> nonFirstChars; private ImmutableSet<Character> nonFirstChars;


/** Source of randomness */ /** Source of randomness */
private final Random random; private final Random random;


/** List of reserved names; these are not returned by generateNextName */ /** List of reserved names; these are not returned by generateNextName */
private Set<String> reservedNames; private ImmutableSet<String> reservedNames;


/** Prefix added to all generated names */ /** Prefix added to all generated names */
private String prefix; private String prefix;
Expand All @@ -98,14 +98,13 @@ public final class RandomNameGenerator implements NameGenerator {
private static final int NUM_SHUFFLES = 16; private static final int NUM_SHUFFLES = 16;


/** Randomly-shuffled version of firstChars */ /** Randomly-shuffled version of firstChars */
private List<Character> shuffledFirst; private String shuffledFirst;
/** Randomly-shuffled versions of nonFirstChard (there are NUM_SUFFLES of /** Randomly-shuffled versions of nonFirstChars (there are NUM_SHUFFLES of them) */
* them) */ private ImmutableList<String> shuffledNonFirst;
private List<List<Character>> shuffledNonFirst;


public RandomNameGenerator(Random random) { public RandomNameGenerator(Random random) {
this.random = random; this.random = random;
reset(new HashSet<String>(), "", null); reset(ImmutableSet.<String>of(), "", null);
} }


RandomNameGenerator( RandomNameGenerator(
Expand Down Expand Up @@ -155,16 +154,17 @@ public void reset(
String prefix, String prefix,
@Nullable char[] reservedFirstCharacters, @Nullable char[] reservedFirstCharacters,
@Nullable char[] reservedNonFirstCharacters) { @Nullable char[] reservedNonFirstCharacters) {
this.reservedNames = reservedNames; this.reservedNames = ImmutableSet.copyOf(reservedNames);
this.prefix = prefix; this.prefix = prefix;
nameCount = 0; nameCount = 0;


// Build the character arrays to use // Build the character arrays to use
this.firstChars = reserveCharacters(FIRST_CHAR, reservedFirstCharacters); firstChars = Sets.difference(FIRST_CHAR, asSet(reservedFirstCharacters)).immutableCopy();
this.nonFirstChars = reserveCharacters(NONFIRST_CHAR, reservedNonFirstCharacters); nonFirstChars =
Sets.difference(NONFIRST_CHAR, asSet(reservedNonFirstCharacters)).immutableCopy();


checkPrefix(prefix); checkPrefix(prefix);
shuffleAlphabets(random); shuffleAlphabets();
} }


@Override @Override
Expand All @@ -176,27 +176,8 @@ public NameGenerator clone(
reservedNames, prefix, reservedCharacters, random); reservedNames, prefix, reservedCharacters, random);
} }


/** private static ImmutableSet<Character> asSet(@Nullable char[] chars) {
* Provides the array of available characters based on the specified arrays. return chars == null ? ImmutableSet.<Character>of() : ImmutableSet.copyOf(Chars.asList(chars));
* Also nicely converts to LinkedHashSet<Char> which is useful later.
*
* @param chars The list of characters that are legal
* @param reservedCharacters The characters that should not be used
* @return An array of characters to use; order from {@code chars} is
* preserved
*/
private LinkedHashSet<Character> reserveCharacters(
char[] chars, char[] reservedCharacters) {
if (reservedCharacters == null) {
reservedCharacters = new char[0];
}

// A LinkedHashSet has a defined iteration ordering, which is that of
// insertion.
LinkedHashSet<Character> result = Sets.newLinkedHashSet(
Chars.asList(chars));
result.removeAll(Chars.asList(reservedCharacters));
return result;
} }


/** /**
Expand All @@ -220,35 +201,20 @@ private void checkPrefix(String prefix) {
} }
} }


private List<Character> shuffleAndCopyAlphabet( private static String shuffleAndCopyAlphabet(Set<Character> input, Random random) {
Iterable<Character> input, Random random) { List<Character> shuffled = new ArrayList<>(input);
List<Character> shuffled = Lists.newArrayList(input);
Collections.shuffle(shuffled, random); Collections.shuffle(shuffled, random);
return shuffled; return new String(Chars.toArray(shuffled));
} }


/** /** Generates random shuffles of the alphabets. */
* Generates random shuffles of the alphabets. private void shuffleAlphabets() {
*/
private void shuffleAlphabets(Random random) {
shuffledFirst = shuffleAndCopyAlphabet(firstChars, random); shuffledFirst = shuffleAndCopyAlphabet(firstChars, random);
shuffledNonFirst = Lists.newArrayList(); ImmutableList.Builder<String> builder = ImmutableList.builder();
for (int i = 0; i < NUM_SHUFFLES; ++i) { for (int i = 0; i < NUM_SHUFFLES; ++i) {
shuffledNonFirst.add(shuffleAndCopyAlphabet(nonFirstChars, random)); builder.add(shuffleAndCopyAlphabet(nonFirstChars, random));
}
}

/**
* Gets the alphabet to use for a character at position {@code position}
* (0-based) given a previous history for that name stored in {@code past}
*/
private List<Character> getAlphabet(int position, Hasher past) {
if (position == 0) {
return shuffledFirst;
} else {
int alphabetIdx = (past.hash().asInt() & 0x7fffffff) % NUM_SHUFFLES;
return shuffledNonFirst.get(alphabetIdx);
} }
shuffledNonFirst = builder.build();
} }


/** /**
Expand All @@ -274,24 +240,29 @@ private int getNameLength(int position, int nameIdx) {
* is supposed to go at position {@code position} in the final name * is supposed to go at position {@code position} in the final name
*/ */
private String generateSuffix(int position, int nameIdx) { private String generateSuffix(int position, int nameIdx) {
String name = ""; StringBuilder name = new StringBuilder();
int length = getNameLength(position, nameIdx); int length = getNameLength(position, nameIdx);
Hasher hasher = Hashing.murmur3_128().newHasher();
hasher.putInt(length);
nameIdx++; nameIdx++;
do { do {
nameIdx--; nameIdx--;
List<Character> alphabet = getAlphabet(position, hasher); String alphabet;
int alphabetSize = alphabet.size(); if (position == 0) {

alphabet = shuffledFirst;
Character character = alphabet.get(nameIdx % alphabetSize); } else {
name += character; Hasher hasher = Hashing.murmur3_128().newHasher();
hasher.putChar(character); 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; nameIdx /= alphabetSize;
position++; position++;
} while (nameIdx > 0); } while (nameIdx > 0);
return name; return name.toString();
} }


/** /**
Expand All @@ -303,8 +274,7 @@ private String generateSuffix(int position, int nameIdx) {
@Override @Override
public String generateNextName() { public String generateNextName() {
while (true) { while (true) {
String name = prefix; String name = prefix + generateSuffix(prefix.length(), nameCount++);
name += generateSuffix(prefix.length(), nameCount++);


// Make sure it's not a JS keyword or reserved name. // Make sure it's not a JS keyword or reserved name.
if (TokenStream.isKeyword(name) || reservedNames.contains(name)) { if (TokenStream.isKeyword(name) || reservedNames.contains(name)) {
Expand Down
20 changes: 10 additions & 10 deletions test/com/google/javascript/jscomp/RandomNameGeneratorTest.java
Expand Up @@ -72,9 +72,9 @@ public static void testGenerate() throws Exception {
reservedNames, prefix, null, random); reservedNames, prefix, null, random);
// Generate all 1- and 2-character names. // Generate all 1- and 2-character names.
// alphabet length, 1st digit // alphabet length, 1st digit
int len1 = RandomNameGenerator.NONFIRST_CHAR.length; int len1 = RandomNameGenerator.NONFIRST_CHAR.size();
// alphabet length, 2nd digit // alphabet length, 2nd digit
int len2 = RandomNameGenerator.NONFIRST_CHAR.length; int len2 = RandomNameGenerator.NONFIRST_CHAR.size();
// len1 == len2 because we have a prefix // len1 == len2 because we have a prefix
int count = len1 * (1 + len2); int count = len1 * (1 + len2);
String[] result = generate(ng, count); String[] result = generate(ng, count);
Expand Down Expand Up @@ -128,8 +128,8 @@ public static void testFirstCharAlphabet() throws Exception {
RandomNameGenerator ng = new RandomNameGenerator( RandomNameGenerator ng = new RandomNameGenerator(
reservedNames, "", null, random); reservedNames, "", null, random);
// Generate all 1- and 2-character names. // Generate all 1- and 2-character names.
int len1 = RandomNameGenerator.FIRST_CHAR.length; int len1 = RandomNameGenerator.FIRST_CHAR.size();
int len2 = RandomNameGenerator.NONFIRST_CHAR.length; int len2 = RandomNameGenerator.NONFIRST_CHAR.size();
int count = len1 * (1 + len2); int count = len1 * (1 + len2);
String[] result = generate(ng, count); String[] result = generate(ng, count);
Set<String> resultSet = Sets.newHashSet(result); Set<String> resultSet = Sets.newHashSet(result);
Expand Down Expand Up @@ -160,8 +160,8 @@ public static void testPrefix() throws Exception {
RandomNameGenerator ng = new RandomNameGenerator( RandomNameGenerator ng = new RandomNameGenerator(
reservedNames, prefix, null, random); reservedNames, prefix, null, random);
// Generate all 1- and 2-character names. // Generate all 1- and 2-character names.
int len1 = RandomNameGenerator.FIRST_CHAR.length; int len1 = RandomNameGenerator.FIRST_CHAR.size();
int len2 = RandomNameGenerator.NONFIRST_CHAR.length; int len2 = RandomNameGenerator.NONFIRST_CHAR.size();
int count = len1 * (1 + len2); int count = len1 * (1 + len2);
String[] result = generate(ng, count); String[] result = generate(ng, count);


Expand Down Expand Up @@ -202,8 +202,8 @@ public static void testReservedNames() throws Exception {
reservedNames, "", null, random); reservedNames, "", null, random);
// Generate all 1- and 2-character names (and a couple 3-character names, // Generate all 1- and 2-character names (and a couple 3-character names,
// because "x" and "ba", and keywords, shouldn't be used). // because "x" and "ba", and keywords, shouldn't be used).
int count = RandomNameGenerator.FIRST_CHAR.length int count =
* (RandomNameGenerator.NONFIRST_CHAR.length + 1); RandomNameGenerator.FIRST_CHAR.size() * (RandomNameGenerator.NONFIRST_CHAR.size() + 1);
Set<String> result = Sets.newHashSet(generate(ng, count)); Set<String> result = Sets.newHashSet(generate(ng, count));


assertThat(result).doesNotContain("x"); assertThat(result).doesNotContain("x");
Expand All @@ -221,8 +221,8 @@ public static void testReservedCharacters() throws Exception {
reservedNames, "", new char[]{'a', 'b'}, random); reservedNames, "", new char[]{'a', 'b'}, random);
// Generate all 1- and 2-character names (and also many 3-character names, // Generate all 1- and 2-character names (and also many 3-character names,
// because "a" and "b" shouldn't be used). // because "a" and "b" shouldn't be used).
int count = RandomNameGenerator.FIRST_CHAR.length int count =
* (RandomNameGenerator.NONFIRST_CHAR.length + 1); RandomNameGenerator.FIRST_CHAR.size() * (RandomNameGenerator.NONFIRST_CHAR.size() + 1);
Set<String> result = Sets.newHashSet(generate(ng, count)); Set<String> result = Sets.newHashSet(generate(ng, count));


assertThat(result).doesNotContain("a"); assertThat(result).doesNotContain("a");
Expand Down

0 comments on commit 5fb2076

Please sign in to comment.