-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add random BIC generation (closes #338)
RandomBic is heavily inspired by RandomIban and provide nearly the same set of methods.
- Loading branch information
1 parent
4ff47b2
commit 3d53b1c
Showing
4 changed files
with
287 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
src/main/java/fr/marcwrobel/jbanking/bic/RandomBic.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package fr.marcwrobel.jbanking.bic; | ||
|
||
import static fr.marcwrobel.jbanking.swift.SwiftPatternCharacterRepresentation.DIGITS; | ||
import static fr.marcwrobel.jbanking.swift.SwiftPatternCharacterRepresentation.UPPER_CASE_LETTERS; | ||
import static java.lang.String.format; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
import fr.marcwrobel.jbanking.IsoCountry; | ||
import fr.marcwrobel.jbanking.IsoCurrency; | ||
import java.util.Arrays; | ||
import java.util.Random; | ||
|
||
/** | ||
* Generates pseudorandom {@link Bic BICs}. | ||
* | ||
* <p> | ||
* Usage: | ||
* | ||
* <pre> | ||
* // Generating a random BIC | ||
* Bic random1 = new RandomBic().next(); | ||
* | ||
* // Generating a random BIC using a given Random (in order to make the generation deterministic) | ||
* Bic random2 = new RandomBic(new Random(0)).next(); | ||
* | ||
* // Generating a random french or german BIC | ||
* Bic random3 = new RandomBic().next(IsoCountry.FR, IsoCountry.DE); | ||
* </pre> | ||
* | ||
* <p> | ||
* This class should only be used for tests. | ||
* | ||
* @since 4.2.0 | ||
*/ | ||
public class RandomBic { | ||
|
||
private static final String LETTERS = UPPER_CASE_LETTERS.alphabet(); | ||
private static final String LETTERS_AND_DIGITS = LETTERS + DIGITS.alphabet(); | ||
|
||
private final Random random; | ||
|
||
/** | ||
* Creates a new random BIC generator using the given {@link Random random number generator}. | ||
* | ||
* @param random a non-null {@link Random} instance | ||
* @throws NullPointerException if the given {@link Random} instance is {@code null} | ||
*/ | ||
public RandomBic(Random random) { | ||
this.random = requireNonNull(random); | ||
} | ||
|
||
/** | ||
* Creates a new random BIC generator. | ||
* | ||
* <p> | ||
* This constructor is creating a new {@link Random random number generator} each time it is | ||
* invoked. | ||
*/ | ||
public RandomBic() { | ||
// Note that Random was chosen over SecureRandom because security does not matter in our case and because Random : | ||
// - produces the same result on all platforms, | ||
// - produces the same results for a seed by default, | ||
// - is random enough, | ||
// - and is much faster. | ||
this(new Random()); | ||
} | ||
|
||
public Bic next() { | ||
return next(IsoCountry.values()); | ||
} | ||
|
||
/** | ||
* Generates a random BIC for one of the given {@link IsoCountry country} (randomly chosen). | ||
* | ||
* @param countries a non-null and non-empty array of {@link IsoCountry} | ||
* @return a non-null {@link Bic} | ||
* @throws NullPointerException if {@code countries} is null | ||
* @throws IllegalArgumentException if {@code countries} is empty | ||
*/ | ||
public Bic next(IsoCountry... countries) { | ||
IsoCountry country = countries[random.nextInt(countries.length)]; | ||
return generate(country); | ||
} | ||
|
||
/** | ||
* Generates a random BIC for one of the given ISO country alpha-2 codes (randomly chosen). | ||
* | ||
* @param isoCountryAlpha2Codes a non-null and non-empty array of ISO country alpha-2 codes | ||
* @return a non-null {@link Bic} | ||
* @throws IllegalArgumentException if {@code isoCountryAlpha2Codes} is empty or if no corresponding {@link IsoCountry} can be | ||
* found for the chosen ISO country alpha-2 code | ||
*/ | ||
public Bic next(String... isoCountryAlpha2Codes) { | ||
String countryCode = isoCountryAlpha2Codes[random.nextInt(isoCountryAlpha2Codes.length)]; | ||
|
||
IsoCountry country = IsoCountry.fromAlpha2Code(countryCode).orElseThrow(() -> new IllegalArgumentException( | ||
format("no corresponding country could be found for alpha-2 code '%s'", countryCode))); | ||
|
||
return generate(country); | ||
} | ||
|
||
/** | ||
* Generates a random BIC for one of the given {@link IsoCurrency currency} (randomly chosen). | ||
* <br> | ||
* This method is not efficient: it needs to build a sorted array of all currencies' countries each time it is invoked. | ||
* | ||
* @param currencies a non-null and non-empty array of {@link IsoCurrency} | ||
* @return a non-null {@link Bic} | ||
* @throws IllegalArgumentException if {@code currencies} is empty. | ||
*/ | ||
public Bic next(IsoCurrency... currencies) { | ||
IsoCountry[] countries = Arrays.stream(currencies) | ||
.flatMap(currency -> currency.getCountries().stream()) | ||
.sorted() | ||
.toArray(IsoCountry[]::new); | ||
return next(countries); | ||
} | ||
|
||
private Bic generate(IsoCountry country) { | ||
StringBuilder bic = new StringBuilder(Bic.BIC11_LENGTH); | ||
|
||
for (int i = 0; i < Bic.INSTITUTION_CODE_LENGTH; i++) { | ||
bic.append(LETTERS.charAt(random.nextInt(LETTERS.length()))); | ||
} | ||
|
||
bic.append(country.getAlpha2Code()); | ||
|
||
for (int i = 0; i < Bic.LOCATION_CODE_LENGTH + Bic.BRANCH_CODE_LENGTH; i++) { | ||
bic.append(LETTERS_AND_DIGITS.charAt(random.nextInt(LETTERS_AND_DIGITS.length()))); | ||
} | ||
|
||
return new Bic(bic.toString()); | ||
} | ||
} |
144 changes: 144 additions & 0 deletions
144
src/test/java/fr/marcwrobel/jbanking/bic/RandomBicTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package fr.marcwrobel.jbanking.bic; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import fr.marcwrobel.jbanking.IsoCountry; | ||
import fr.marcwrobel.jbanking.IsoCurrency; | ||
import java.util.Random; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class RandomBicTest { | ||
|
||
@Test | ||
void whenRandomIsNull_thenThrows() { | ||
assertThrows(NullPointerException.class, () -> new RandomBic(null)); | ||
} | ||
|
||
@Test | ||
void whenRandomIsKnown_thenResultIsDeterministic() { | ||
RandomBic random = new RandomBic(new Random(0)); | ||
Bic bic = random.next(); | ||
assertEquals(new Bic("SXVNKW39VPC"), bic); | ||
} | ||
|
||
@Test | ||
void generatedBicsAreValid() { | ||
RandomBic random = new RandomBic(); | ||
|
||
for (int i = 0; i < IsoCountry.values().length * 10000; i++) { | ||
Bic bic = random.next(); | ||
assertTrue(Bic.isValid(bic.toString())); | ||
} | ||
} | ||
|
||
@Nested | ||
class ByCountry { | ||
|
||
@Test | ||
void whenArrayIsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(NullPointerException.class, () -> random.next((IsoCountry[]) null)); | ||
} | ||
|
||
@Test | ||
void whenArrayContainsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(NullPointerException.class, () -> random.next(new IsoCountry[] { null })); | ||
} | ||
|
||
@Test | ||
void whenArrayIsEmpty_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(IllegalArgumentException.class, () -> random.next(new IsoCountry[] {})); | ||
} | ||
|
||
@Test | ||
void whenRandomIsKnown_thenResultIsDeterministic() { | ||
RandomBic random = new RandomBic(new Random(0)); | ||
Bic bic = random.next(IsoCountry.FR, IsoCountry.DE); | ||
assertEquals(new Bic("SXVNDE39VPC"), bic); | ||
} | ||
|
||
@Test | ||
void whenCountryIsKnown_thenSameBicCountry() { | ||
RandomBic random = new RandomBic(); | ||
Bic bic = random.next(IsoCountry.FR); | ||
assertEquals(IsoCountry.FR, bic.getCountry()); | ||
} | ||
} | ||
|
||
@Nested | ||
class ByCountryCode { | ||
|
||
@Test | ||
void whenArrayIsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(NullPointerException.class, () -> random.next((String[]) null)); | ||
} | ||
|
||
@Test | ||
void whenArrayContainsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(IllegalArgumentException.class, () -> random.next(new String[] { null })); | ||
} | ||
|
||
@Test | ||
void whenArrayIsEmpty_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(IllegalArgumentException.class, () -> random.next(new String[] {})); | ||
} | ||
|
||
@Test | ||
void whenRandomIsKnown_thenResultIsDeterministic() { | ||
RandomBic random = new RandomBic(new Random(0)); | ||
Bic bic = random.next(IsoCountry.JP.getAlpha2Code(), IsoCountry.US.getAlpha2Code()); | ||
assertEquals(new Bic("SXVNUS39VPC"), bic); | ||
} | ||
|
||
@Test | ||
void whenCountryIsKnown_thenSameBicCountry() { | ||
RandomBic random = new RandomBic(); | ||
Bic bic = random.next(IsoCountry.GB.getAlpha2Code()); | ||
assertEquals(IsoCountry.GB, bic.getCountry()); | ||
} | ||
} | ||
|
||
@Nested | ||
class ByCurrency { | ||
|
||
@Test | ||
void whenArrayIsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(NullPointerException.class, () -> random.next((IsoCurrency[]) null)); | ||
} | ||
|
||
@Test | ||
void whenArrayContainsNull_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(NullPointerException.class, () -> random.next(new IsoCurrency[] { null })); | ||
} | ||
|
||
@Test | ||
void whenArrayIsEmpty_thenThrows() { | ||
RandomBic random = new RandomBic(); | ||
assertThrows(IllegalArgumentException.class, () -> random.next(new IsoCurrency[] {})); | ||
} | ||
|
||
@Test | ||
void whenRandomIsKnown_thenResultIsDeterministic() { | ||
RandomBic random = new RandomBic(new Random(0)); | ||
Bic bic = random.next(IsoCurrency.EUR, IsoCurrency.USD); | ||
assertEquals(new Bic("SXVNDE39VPC"), bic); | ||
} | ||
|
||
@Test | ||
void whenCountryIsKnown_thenSameBicCountry() { | ||
RandomBic random = new RandomBic(); | ||
Bic bic = random.next(IsoCurrency.JPY); | ||
assertEquals(IsoCountry.JP, bic.getCountry()); | ||
} | ||
} | ||
} |