Skip to content

Commit b07303f

Browse files
committed
refactor: centralize random string generation in RandomGenerator
Move random numeric, alphabetic, and alphanumeric logic from static RandomUtils to instance-based RandomGenerator. This ensures all random data generation respects the provided seed, fixing non-deterministic behavior across locale-specific providers. - delete RandomUtils and its tests to remove unseeded random sources - update BaseProducer to delegate random string calls to RandomGenerator - update locale providers (PL, EN, FR, ES) to use the seeded generator - update Groovy Spock tests to reflect the adjusted random sequence caused by consistent generator usage - add comprehensive JUnit 5 tests for RandomGenerator
1 parent f25427d commit b07303f

29 files changed

Lines changed: 294 additions & 323 deletions

src/main/java/com/devskiller/jfairy/LocaleSpecificProvidersFactory.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private static LocaleSpecificProviders createPlProviders(DataMaster dataMaster,
9797
new PlNationalIdentityCardNumberProvider(dateProducer, baseProducer),
9898
new PlVATIdentificationNumberProvider(baseProducer),
9999
new PlAddressProvider(dataMaster, baseProducer),
100-
new PlPassportNumberProvider()
100+
new PlPassportNumberProvider(baseProducer)
101101
);
102102
}
103103

@@ -122,7 +122,7 @@ private static LocaleSpecificProviders createEnProviders(DataMaster dataMaster,
122122
new EnNationalIdentityCardNumberProvider(baseProducer),
123123
new EnVATIdentificationNumberProvider(baseProducer),
124124
new EnAddressProvider(dataMaster, baseProducer),
125-
new EnPassportNumberProvider()
125+
new EnPassportNumberProvider(baseProducer)
126126
);
127127
}
128128

@@ -134,7 +134,7 @@ private static LocaleSpecificProviders createFrProviders(DataMaster dataMaster,
134134
new EnNationalIdentityCardNumberProvider(baseProducer),
135135
new EnVATIdentificationNumberProvider(baseProducer),
136136
new FrAddressProvider(dataMaster, baseProducer),
137-
new EnPassportNumberProvider()
137+
new EnPassportNumberProvider(baseProducer)
138138
);
139139
}
140140

@@ -143,10 +143,10 @@ private static LocaleSpecificProviders createEsProviders(DataMaster dataMaster,
143143
DateProducer dateProducer) {
144144
return new LocaleSpecificProviders(
145145
new NoNationalIdentificationNumberFactory(),
146-
new EsNationalIdentityCardNumberProvider(),
147-
new EsVATIdentificationNumberProvider(),
146+
new EsNationalIdentityCardNumberProvider(baseProducer),
147+
new EsVATIdentificationNumberProvider(baseProducer),
148148
new EsAddressProvider(dataMaster, baseProducer),
149-
new EsPassportNumberProvider()
149+
new EsPassportNumberProvider(baseProducer)
150150
);
151151
}
152152

@@ -159,7 +159,7 @@ private static LocaleSpecificProviders createSvProviders(DataMaster dataMaster,
159159
new SvNationalIdentityCardNumberProvider(dateProducer, baseProducer),
160160
new SvVATIdentificationNumberProvider(baseProducer, dateProducer, nationalIdFactory),
161161
new SvAddressProvider(dataMaster, baseProducer),
162-
new SvPassportNumberProvider()
162+
new SvPassportNumberProvider(baseProducer)
163163
);
164164
}
165165

@@ -171,7 +171,7 @@ private static LocaleSpecificProviders createZhProviders(DataMaster dataMaster,
171171
new ZhNationalIdentityCardNumberProvider(baseProducer),
172172
new ZhVATIdentificationNumberProvider(),
173173
new ZhAddressProvider(dataMaster, baseProducer),
174-
new ZhPassportNumberProvider()
174+
new ZhPassportNumberProvider(baseProducer)
175175
);
176176
}
177177

@@ -181,7 +181,7 @@ private static LocaleSpecificProviders createDeProviders(DataMaster dataMaster,
181181
return new LocaleSpecificProviders(
182182
new NoNationalIdentificationNumberFactory(),
183183
new DeNationalIdentityCardNumberProvider(baseProducer),
184-
new DeVATIdentificationNumberProvider(),
184+
new DeVATIdentificationNumberProvider(baseProducer),
185185
new DeAddressProvider(dataMaster, baseProducer),
186186
new DePassportNumberProvider(baseProducer)
187187
);

src/main/java/com/devskiller/jfairy/producer/BaseProducer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ public double randomBetween(double min, double max) {
126126
return random.nextDouble(min, max);
127127
}
128128

129+
public String randomNumeric(int length) {
130+
return random.randomNumeric(length);
131+
}
132+
133+
public String randomAlphabetic(int length) {
134+
return random.randomAlphabetic(length);
135+
}
136+
137+
public String randomAlphanumeric(int length) {
138+
return random.randomAlphanumeric(length);
139+
}
140+
129141
/**
130142
* Replaces all {@code '?'} characters with random chars from [a - z] range
131143
*

src/main/java/com/devskiller/jfairy/producer/RandomGenerator.java

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,129 @@
44
import java.util.List;
55
import java.util.Random;
66

7+
/**
8+
* Source of random data for all jFairy producers.
9+
* <p>
10+
* This class encapsulates a {@link Random} instance to ensure determinism
11+
* when a fixed seed is provided.
12+
*/
713
public class RandomGenerator {
814

15+
private static final String NUMERIC_CHARS = "0123456789";
16+
private static final String ALPHA_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
17+
private static final String ALPHANUMERIC_CHARS = NUMERIC_CHARS + ALPHA_CHARS;
18+
919
private final Random random;
1020

21+
/**
22+
* Creates a new generator with a default seed.
23+
*/
1124
public RandomGenerator() {
1225
this.random = new Random();
1326
}
1427

28+
/**
29+
* Creates a new generator with a fixed seed for deterministic behavior.
30+
*
31+
* @param seed the initial seed
32+
*/
1533
public RandomGenerator(int seed) {
1634
this.random = new Random(seed);
1735
}
1836

37+
/**
38+
* Returns the next pseudorandom boolean value.
39+
*
40+
* @return a random boolean
41+
*/
1942
public boolean nextBoolean() {
2043
return random.nextBoolean();
2144
}
2245

46+
/**
47+
* Randomly permutes the specified list.
48+
*
49+
* @param <T> the type of elements in the list
50+
* @param elements the list to be shuffled
51+
* @return the shuffled list
52+
*/
2353
public <T> List<T> shuffle(List<T> elements) {
2454
Collections.shuffle(elements, random);
2555
return elements;
2656
}
2757

58+
/**
59+
* Returns a random integer between min and max (inclusive).
60+
*
61+
* @param min lower bound (inclusive)
62+
* @param max upper bound (inclusive)
63+
* @return a random integer
64+
*/
2865
public int nextInt(int min, int max) {
29-
if (min == max) {
30-
return min;
31-
}
32-
return random.nextInt(min, max + 1);
66+
return min == max ? min : random.nextInt(min, max + 1);
3367
}
3468

69+
/**
70+
* Returns a random long between min and max (inclusive).
71+
*
72+
* @param min lower bound (inclusive)
73+
* @param max upper bound (inclusive)
74+
* @return a random long
75+
*/
3576
public long nextLong(long min, long max) {
36-
if (min == max) {
37-
return min;
38-
}
39-
return random.nextLong(min, max + 1);
77+
return min == max ? min : random.nextLong(min, max + 1);
4078
}
4179

80+
/**
81+
* Returns a random double between min (inclusive) and max (exclusive).
82+
*
83+
* @param min lower bound (inclusive)
84+
* @param max upper bound (exclusive)
85+
* @return a random double
86+
*/
4287
public double nextDouble(double min, double max) {
43-
if (min == max) {
44-
return min;
88+
return min == max ? min : random.nextDouble(min, max);
89+
}
90+
91+
/**
92+
* Generates a random numeric string.
93+
*
94+
* @param length desired length
95+
* @return string of digits
96+
*/
97+
public String randomNumeric(int length) {
98+
return randomString(length, NUMERIC_CHARS);
99+
}
100+
101+
/**
102+
* Generates a random alphabetic string (upper-case ASCII).
103+
*
104+
* @param length desired length
105+
* @return string of letters
106+
*/
107+
public String randomAlphabetic(int length) {
108+
return randomString(length, ALPHA_CHARS);
109+
}
110+
111+
/**
112+
* Generates a random alphanumeric string (upper-case ASCII).
113+
*
114+
* @param length desired length
115+
* @return string of digits and letters
116+
*/
117+
public String randomAlphanumeric(int length) {
118+
return randomString(length, ALPHANUMERIC_CHARS);
119+
}
120+
121+
private String randomString(int length, String alphabet) {
122+
if (length < 0) {
123+
throw new IllegalArgumentException("length must be >= 0, got: " + length);
124+
}
125+
StringBuilder sb = new StringBuilder(length);
126+
for (int i = 0; i < length; i++) {
127+
sb.append(alphabet.charAt(random.nextInt(alphabet.length())));
45128
}
46-
return random.nextDouble(min, max);
129+
return sb.toString();
47130
}
131+
48132
}

src/main/java/com/devskiller/jfairy/producer/company/locale/de/DeVATIdentificationNumberProvider.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.devskiller.jfairy.producer.company.locale.de;
22

3+
import com.devskiller.jfairy.producer.BaseProducer;
34
import com.devskiller.jfairy.producer.VATIdentificationNumberProvider;
45

5-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomNumeric;
6-
76
/**
87
* German VAT identification number (Umsatzsteuer-Identifikationsnummer or USt-IdNr.)
98
* <p>
@@ -15,9 +14,15 @@ public class DeVATIdentificationNumberProvider implements VATIdentificationNumbe
1514

1615
private static final String VALID_NUMBER_PATTERN = "^[0-9]{9}$";
1716

17+
private final BaseProducer baseProducer;
18+
19+
public DeVATIdentificationNumberProvider(BaseProducer baseProducer) {
20+
this.baseProducer = baseProducer;
21+
}
22+
1823
@Override
1924
public String get() {
20-
return randomNumeric(9);
25+
return baseProducer.randomNumeric(9);
2126
}
2227

2328
public boolean isValid(String vatIdentificationNumber) {

src/main/java/com/devskiller/jfairy/producer/company/locale/es/EsVATIdentificationNumberProvider.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22

33
import java.util.regex.Pattern;
44

5+
import com.devskiller.jfairy.producer.BaseProducer;
56
import com.devskiller.jfairy.producer.VATIdentificationNumberProvider;
67

7-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomAlphabetic;
8-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomAlphanumeric;
9-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomNumeric;
10-
118
/**
129
* Spanish VAT Identification Number (known as Número de Identificación Fiscal (for freelancers) or Código de Identificación Fiscal (for companies) in Spain)
1310
* <p>
@@ -17,16 +14,21 @@ public class EsVATIdentificationNumberProvider implements VATIdentificationNumbe
1714

1815
private static final String REGEX_CIF = "^[A-Z][0-9]{2}[0-9]{5}([KPQSABEH]|[0-9]|[A-Z])$";
1916

17+
private final BaseProducer baseProducer;
18+
2019
private final Pattern regexCif;
2120

22-
public EsVATIdentificationNumberProvider() {
21+
public EsVATIdentificationNumberProvider(BaseProducer baseProducer) {
22+
this.baseProducer = baseProducer;
2323
this.regexCif = Pattern.compile(REGEX_CIF);
2424
}
2525

2626
@Override
2727
public String get() {
2828
return String.format("%s%s%s",
29-
randomAlphabetic(1).toUpperCase(), randomNumeric(7), randomAlphanumeric(1).toUpperCase());
29+
baseProducer.randomAlphabetic(1).toUpperCase(),
30+
baseProducer.randomNumeric(7),
31+
baseProducer.randomAlphanumeric(1).toUpperCase());
3032
}
3133

3234
public boolean isValid(String cif) {

src/main/java/com/devskiller/jfairy/producer/company/locale/sv/SvVATIdentificationNumberProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import static com.devskiller.jfairy.producer.person.NationalIdentificationNumberProperties.dateOfBirth;
1414
import static com.devskiller.jfairy.producer.person.locale.sv.SvNationalIdentificationNumberProvider.calculateChecksum;
15-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomNumeric;
1615
import static com.devskiller.jfairy.producer.util.StringUtils.leftPad;
1716

1817
/**
@@ -46,7 +45,8 @@ public String get() {
4645

4746
int randomGroupNumber = baseProducer.randomElement(GroupNumber.class).getValue();
4847
String randomNumericBetween20And99 = leftPad(valueOf(baseProducer.randomBetween(20, 99)), 2, "0");
49-
String organizationNumberWithoutChecksum = randomGroupNumber + randomNumeric(1) + randomNumericBetween20And99 + randomNumeric(5);
48+
String organizationNumberWithoutChecksum = randomGroupNumber + baseProducer.randomNumeric(1)
49+
+ randomNumericBetween20And99 + baseProducer.randomNumeric(5);
5050
String organizationNumber = organizationNumberWithoutChecksum + calculateChecksum(organizationNumberWithoutChecksum);
5151

5252
return SE + organizationNumber + "01";

src/main/java/com/devskiller/jfairy/producer/person/locale/de/DeNationalIdentityCardNumberProvider.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import com.devskiller.jfairy.producer.BaseProducer;
44
import com.devskiller.jfairy.producer.person.NationalIdentityCardNumberProvider;
55

6-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomNumeric;
7-
86
/**
97
* German Identity Card (known as Personalausweis)
108
* <p>
@@ -26,7 +24,7 @@ public DeNationalIdentityCardNumberProvider(BaseProducer baseProducer) {
2624

2725
@Override
2826
public String get() {
29-
return baseProducer.randomElement(ID_CARD_TYPE_LETTERS) + randomNumeric(8);
27+
return baseProducer.randomElement(ID_CARD_TYPE_LETTERS) + baseProducer.randomNumeric(8);
3028
}
3129

3230
public boolean isValid(String nationalIdentityCardNumber) {

src/main/java/com/devskiller/jfairy/producer/person/locale/de/DePassportNumberProvider.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import com.devskiller.jfairy.producer.BaseProducer;
44
import com.devskiller.jfairy.producer.person.PassportNumberProvider;
55

6-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomNumeric;
7-
86
/**
97
* German passport (known as Reisepass)
108
* <p>
@@ -26,7 +24,7 @@ public DePassportNumberProvider(BaseProducer baseProducer) {
2624

2725
@Override
2826
public String get() {
29-
return baseProducer.randomElement(PASSPORT_TYPE_LETTERS) + randomNumeric(8);
27+
return baseProducer.randomElement(PASSPORT_TYPE_LETTERS) + baseProducer.randomNumeric(8);
3028
}
3129

3230
public boolean isValid(String passportNumber) {
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package com.devskiller.jfairy.producer.person.locale.en;
22

3+
import com.devskiller.jfairy.producer.BaseProducer;
34
import com.devskiller.jfairy.producer.person.PassportNumberProvider;
45

5-
import static com.devskiller.jfairy.producer.util.RandomUtils.randomAlphanumeric;
6-
76
/**
87
* @author Olga Maciaszek-Sharma
98
* @since 15.03.15
109
*/
1110
public class EnPassportNumberProvider implements PassportNumberProvider {
1211

12+
private final BaseProducer baseProducer;
13+
14+
public EnPassportNumberProvider(BaseProducer baseProducer) {
15+
this.baseProducer = baseProducer;
16+
}
17+
1318
@Override
1419
public String get() {
15-
return randomAlphanumeric(9);
20+
return baseProducer.randomAlphanumeric(9);
1621
}
1722
}

0 commit comments

Comments
 (0)