Skip to content

Commit

Permalink
Merge pull request marcwrobel#177 from marcwrobel/170-bic-regex
Browse files Browse the repository at this point in the history
Get rid of regexes in Bic
  • Loading branch information
marcwrobel authored Jul 26, 2022
2 parents 2776760 + 6c0bf14 commit 9c96481
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 23 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Changed

- (breaking change) Make `CreditorIdentifier` final (#116).
- (**breaking change**) Make `CreditorIdentifier` final (#116).
- (**breaking change**) Rename `Bic#BIC_REGEX` to `Bic#REGEX` (as part of #170).
- Get rid of regexes to validate BICs (#170). This significantly increased the performances of BIC validation (x3) and
creation (x4).

### Fixed

### Deprecated

### Removed

- (**breaking change**) Remove `Bic#BIC_PATTERN` (as part of #170). If you still need to use the BIC regex, you may compile
the pattern from `Bic#REGEX`, which has been kept for compatibility and documentation purposes.

### Internal

- Alphabetically sort `IsoCurrency`, `Holidays` and `BbanStructure` enums entries (#161).
Expand Down
14 changes: 7 additions & 7 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ All test were run with in the following conditions :
| b.BicBenchmark.creation | AECFFR21 | 1.0 | 2,444,715 | N/A |
| b.BicBenchmark.creation | AECFFR21 | 2.1.0 | 2,481,546 | +1.48% |
| b.BicBenchmark.creation | AECFFR21 | 3.4.0 | 6,649,405 | +62.68% |
| b.BicBenchmark.creation | AECFFR21 | 4.0.0-SNAPSHOT | 7,629,868 | +12.85% |
| b.BicBenchmark.creation | AECFFR21 | 4.0.0-SNAPSHOT | 19,604,576 | +194.83% |
| b.BicBenchmark.creation | AECFFR21XXX | 1.0 | 2,512,454 | N/A |
| b.BicBenchmark.creation | AECFFR21XXX | 2.1.0 | 2,550,922 | +1.51% |
| b.BicBenchmark.creation | AECFFR21XXX | 4.0.0-SNAPSHOT | 7,746,081 | +67.07% |
| b.BicBenchmark.creation | AECFFR21XXX | 3.4.0 | 8,066,353 | +3.97% |
| b.BicBenchmark.creation | AECFFR21XXX | 3.4.0 | 8,066,353 | +216.21 |
| b.BicBenchmark.creation | AECFFR21XXX | 4.0.0-SNAPSHOT | 23,230,205 | +187.98 |
| b.BicBenchmark.validation | AECFFR21 | 1.0 | 2,701,550 | N/A |
| b.BicBenchmark.validation | AECFFR21 | 2.1.0 | 2,831,007 | +4.57% |
| b.BicBenchmark.validation | AECFFR21 | 3.4.0 | 8,874,888 | +68.10% |
| b.BicBenchmark.validation | AECFFR21 | 4.0.0-SNAPSHOT | 9,334,088 | +4.92% |
| b.BicBenchmark.validation | AECFFR21 | 3.4.0 | 8,874,888 | +302.85% |
| b.BicBenchmark.validation | AECFFR21 | 4.0.0-SNAPSHOT | 35,753,064 | 309.83% |
| b.BicBenchmark.validation | AECFFR21XXX | 2.1.0 | 2,527,600 | N/A |
| b.BicBenchmark.validation | AECFFR21XXX | 1.0 | 2,676,293 | +5.56% |
| b.BicBenchmark.validation | AECFFR21XXX | 4.0.0-SNAPSHOT | 7,663,691 | +65.08% |
| b.BicBenchmark.validation | AECFFR21XXX | 3.4.0 | 7,797,982 | +1.72% |
| b.BicBenchmark.validation | AECFFR21XXX | 3.4.0 | 7,797,982 | +191.37% |
| b.BicBenchmark.validation | AECFFR21XXX | 4.0.0-SNAPSHOT | 32,069,027 | +311.24% |
| b.CalendarBenchmark.validation | NEW_YORK_FED | 4.0.0-SNAPSHOT | 1,464,062 | N/A |
| b.CalendarBenchmark.validation | NEW_YORK_FED | 3.4.0 | 1,508,236 | +2.93% |
| b.CalendarBenchmark.validation | PARIS | 3.4.0 | 2,703,850 | N/A |
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/run
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Result:

def set_increase(self, other):
if other is not None and self.name == other.name and self.parameter == other.parameter:
self.increase = ((self.throughput - other.throughput) / self.throughput)
self.increase = ((self.throughput - other.throughput) / other.throughput) * 100.0


BENCHMARKS = {"Iban", "Bic", "CreditorIdentifier", "Calendar"}
Expand Down Expand Up @@ -132,7 +132,7 @@ with open("{dir}/jbanking-benchmarks.md".format(dir=base_dir), "w") as f:
"| {n} | {p} | {v} | {t} | {i} |\n"
.format(n=current.name, p=current.parameter, v=current.version,
t="{:,.0f}".format(current.throughput),
i='N/A' if current.increase is None else "{:,.2}%".format(current.increase))
i='N/A' if current.increase is None else "{:,.2f}%".format(current.increase))
])

print("Benchmark finished, results can be found in " + base_dir)
32 changes: 23 additions & 9 deletions src/main/java/fr/marcwrobel/jbanking/bic/Bic.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package fr.marcwrobel.jbanking.bic;

import fr.marcwrobel.jbanking.IsoCountry;
import fr.marcwrobel.jbanking.internal.AsciiCharacters;
import java.io.Serializable;
import java.util.Optional;
import java.util.regex.Pattern;

/**
* A Business Identifier Code (also known as BIC, SWIFT-BIC, BIC code, SWIFT ID or SWIFT code, Business Entity Identifier or
Expand Down Expand Up @@ -56,12 +56,8 @@ public final class Bic implements Serializable {
/**
* A simple regex that validate well-formed BIC.
*/
public static final String BIC_REGEX = "[A-Za-z]{4}[A-Za-z]{2}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?";

/**
* A pre-compiled Pattern for {@link #BIC_REGEX}.
*/
public static final Pattern BIC_PATTERN = Pattern.compile(BIC_REGEX);
@SuppressWarnings("unused") // kept for compatibility and documentation purposes
public static final String REGEX = "[A-Za-z]{4}[A-Za-z]{2}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?";

/**
* The branch code for primary offices.
Expand All @@ -74,6 +70,7 @@ public final class Bic implements Serializable {
public static final char TEST_BIC_INDICATOR = '0';

private static final int BIC8_LENGTH = 8;
private static final int BIC11_LENGTH = 11;
private static final int INSTITUTION_CODE_INDEX = 0;
private static final int INSTITUTION_CODE_LENGTH = 4;
private static final int COUNTRY_CODE_INDEX = INSTITUTION_CODE_INDEX + INSTITUTION_CODE_LENGTH;
Expand All @@ -96,7 +93,7 @@ public final class Bic implements Serializable {
*
* @param bic8Or11 A non-null String.
* @throws IllegalArgumentException if the given string is {@code null}
* @throws BicFormatException if the given BIC8 or BIC11 string does not match {@link #BIC_REGEX} or if the given BIC8 or
* @throws BicFormatException if the given BIC8 or BIC11 string does not match {@value #REGEX} or if the given BIC8 or
* BIC11 country code is not known in {@link fr.marcwrobel.jbanking.IsoCountry}.
*/
public Bic(final String bic8Or11) {
Expand Down Expand Up @@ -131,7 +128,24 @@ public static boolean isValid(String bic) {
}

private static boolean isWellFormatted(String s) {
return BIC_PATTERN.matcher(s).matches();
int length = s.length();
if (length != BIC8_LENGTH && length != BIC11_LENGTH) {
return false;
}

for (int i = 0; i < INSTITUTION_CODE_INDEX + INSTITUTION_CODE_LENGTH + COUNTRY_CODE_LENGTH; i++) {
if (!AsciiCharacters.isAlphabetic(s.charAt(i))) {
return false;
}
}

for (int i = LOCATION_CODE_INDEX; i < length; i++) {
if (!AsciiCharacters.isAlphanumeric(s.charAt(i))) {
return false;
}
}

return true;
}

private static Optional<IsoCountry> findCountryFor(String s) {
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/fr/marcwrobel/jbanking/internal/AsciiCharacters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package fr.marcwrobel.jbanking.internal;

/**
* Code from this class is an adaptation of <a href="https://commons.apache.org/proper/commons-lang/">Apache commons-lang</a>
* {@code CharUtils}.
*/
public class AsciiCharacters {

/**
* <p>
* Checks whether the character is ASCII 7 bit alphabetic.
* </p>
*
* <pre>
* CharUtils.isAsciiAlpha('a') = true
* CharUtils.isAsciiAlpha('A') = true
* CharUtils.isAsciiAlpha('3') = false
* CharUtils.isAsciiAlpha('-') = false
* CharUtils.isAsciiAlpha('\n') = false
* CharUtils.isAsciiAlpha('&copy;') = false
* </pre>
*
* @param c the character to check
* @return true if between 65 and 90 or 97 and 122 inclusive
*/
public static boolean isAlphabetic(final char c) {
return isUpperAlphabetic(c) || isLowerAlphabetic(c);
}

/**
* <p>
* Checks whether the character is ASCII 7 bit alphabetic upper case.
* </p>
*
* <pre>
* CharUtils.isAsciiAlphaUpper('a') = false
* CharUtils.isAsciiAlphaUpper('A') = true
* CharUtils.isAsciiAlphaUpper('3') = false
* CharUtils.isAsciiAlphaUpper('-') = false
* CharUtils.isAsciiAlphaUpper('\n') = false
* CharUtils.isAsciiAlphaUpper('&copy;') = false
* </pre>
*
* @param c the character to check
* @return true if between 65 and 90 inclusive
*/
public static boolean isUpperAlphabetic(final char c) {
return c >= 'A' && c <= 'Z';
}

/**
* <p>
* Checks whether the character is ASCII 7 bit alphabetic lower case.
* </p>
*
* <pre>
* CharUtils.isAsciiAlphaLower('a') = true
* CharUtils.isAsciiAlphaLower('A') = false
* CharUtils.isAsciiAlphaLower('3') = false
* CharUtils.isAsciiAlphaLower('-') = false
* CharUtils.isAsciiAlphaLower('\n') = false
* CharUtils.isAsciiAlphaLower('&copy;') = false
* </pre>
*
* @param c the character to check
* @return true if between 97 and 122 inclusive
*/
public static boolean isLowerAlphabetic(final char c) {
return c >= 'a' && c <= 'z';
}

/**
* <p>
* Checks whether the character is ASCII 7 bit numeric.
* </p>
*
* <pre>
* CharUtils.isAsciiNumeric('a') = false
* CharUtils.isAsciiNumeric('A') = false
* CharUtils.isAsciiNumeric('3') = true
* CharUtils.isAsciiNumeric('-') = false
* CharUtils.isAsciiNumeric('\n') = false
* CharUtils.isAsciiNumeric('&copy;') = false
* </pre>
*
* @param c the character to check
* @return true if between 48 and 57 inclusive
*/
public static boolean isNumeric(final char c) {
return c >= '0' && c <= '9';
}

/**
* <p>
* Checks whether the character is ASCII 7 bit numeric.
* </p>
*
* <pre>
* CharUtils.isAsciiAlphanumeric('a') = true
* CharUtils.isAsciiAlphanumeric('A') = true
* CharUtils.isAsciiAlphanumeric('3') = true
* CharUtils.isAsciiAlphanumeric('-') = false
* CharUtils.isAsciiAlphanumeric('\n') = false
* CharUtils.isAsciiAlphanumeric('&copy;') = false
* </pre>
*
* @param c the character to check
* @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
*/
public static boolean isAlphanumeric(final char c) {
return isAlphabetic(c) || isNumeric(c);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

/**
* Document the last verification date of a list (of countries, of currencies...).
*
* <p>
* This enum is for jbanking internal use only : do not use it in your code !
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -18,7 +15,7 @@

/**
* Returns the last verification date of the annotated element.
*
*
* @return a non null date formatted as {@code yyyy-MM-dd} (e.g. 2022-07-20).
*/
String value();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Classes in this package are for jbanking internal use only: do not use them !
*/
package fr.marcwrobel.jbanking.internal;

0 comments on commit 9c96481

Please sign in to comment.