Skip to content

Instantly share code, notes, and snippets.

@Sch3lp
Last active March 16, 2020 07:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sch3lp/2fa7afbb99f615fbfb868621cf5544cc to your computer and use it in GitHub Desktop.
Save Sch3lp/2fa7afbb99f615fbfb868621cf5544cc to your computer and use it in GitHub Desktop.
VinValidator
import java.util.Objects;
public class ValidationError {
private final String error;
private ValidationError(final String error) {
this.error = error;
}
public static ValidationError from(final String error) {
return new ValidationError(error);
}
public String getError() {
return error;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ValidationError that = (ValidationError) o;
return Objects.equals(error, that.error);
}
@Override
public int hashCode() {
return Objects.hash(error);
}
@Override
public String toString() {
return "ValidationError{" +
"error='" + error + '\'' +
'}';
}
}
import java.util.Optional;
import static java.lang.Character.isDigit;
public class VinValidator {
private static final int[] VALUES = {1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 0, 7, 0, 9, 2, 3, 4, 5, 6, 7, 8, 9};
private static final int[] WEIGHTS = {8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2};
public static Optional<ValidationError> validate(String vinToValidate) {
if (vinToValidate == null) {
return Optional.of(ValidationError.from("VIN_MANDATORY"));
}
final var vin = cleanUpVin(vinToValidate);
if (vin.length() != 17) {
return Optional.of(ValidationError.from("VIN_MAX_LENGTH"));
}
int sum = 0;
for (int i = 0; i < 17; i++) {
char c = vin.charAt(i);
int value;
// Only accept the 26 letters of the alphabet
if (c >= 'A' && c <= 'Z') {
value = VALUES[c - 'A'];
if (value == 0) {
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER"));
}
} else if (isDigit(c)) {
value = c - '0';
} else { // illegal character
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER"));
}
sum = sum + (WEIGHTS[i] * value);
}
// check digit
sum = sum % 11;
char check = vin.charAt(8);
if (sum == 10 && check == 'X') {
return Optional.empty();
}
if (sum == transliterate(check)) {
return Optional.empty();
}
return Optional.of(ValidationError.from("VIN_ILLEGAL"));
}
private static String cleanUpVin(String value) {
var result = value;
result = result.replaceAll("-", "");
result = result.replaceAll(" ", "");
result = result.toUpperCase();
return result;
}
private static int transliterate(char check) {
if (check == 'A' || check == 'J') {
return 1;
} else if (check == 'B' || check == 'K' || check == 'S') {
return 2;
} else if (check == 'C' || check == 'L' || check == 'T') {
return 3;
} else if (check == 'D' || check == 'M' || check == 'U') {
return 4;
} else if (check == 'E' || check == 'N' || check == 'V') {
return 5;
} else if (check == 'F' || check == 'W') {
return 6;
} else if (check == 'G' || check == 'P' || check == 'X') {
return 7;
} else if (check == 'H' || check == 'Y') {
return 8;
} else if (check == 'R' || check == 'Z') {
return 9;
} else if (Integer.valueOf(Character.getNumericValue(check)) != null) { //hacky but works
return Character.getNumericValue(check);
}
return -1;
}
private VinValidator() {
// static utility class
}
}
import java.util.*
object VinValidator {
private val VALUES = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 0, 7, 0, 9, 2, 3, 4, 5, 6, 7, 8, 9)
private val WEIGHTS = intArrayOf(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2)
fun validate(vinToValidate: String?): Optional<ValidationError> {
if (vinToValidate == null) {
return Optional.of(ValidationError.from("VIN_MANDATORY"))
}
val vin = cleanUpVin(vinToValidate)
if (vin.length != 17) {
return Optional.of(ValidationError.from("VIN_MAX_LENGTH"))
}
var sum = 0
for (i in 0..16) {
val c = vin[i]
var value: Int
// Only accept the 26 letters of the alphabet
if (c >= 'A' && c <= 'Z') {
value = VALUES[c - 'A']
if (value == 0) {
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER"))
}
} else if (Character.isDigit(c)) {
value = c - '0'
} else { // illegal character
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER"))
}
sum = sum + WEIGHTS[i] * value
}
// check digit
sum = sum % 11
val check = vin[8]
if (sum == 10 && check == 'X') {
return Optional.empty<ValidationError>()
}
return if (sum == transliterate(check)) {
Optional.empty<ValidationError>()
} else Optional.of(ValidationError.from("VIN_ILLEGAL"))
}
private fun cleanUpVin(value: String): String {
var result = value
result = result.replace("-".toRegex(), "")
result = result.replace(" ".toRegex(), "")
result = result.toUpperCase()
return result
}
private fun transliterate(check: Char): Int {
if (check == 'A' || check == 'J') {
return 1
} else if (check == 'B' || check == 'K' || check == 'S') {
return 2
} else if (check == 'C' || check == 'L' || check == 'T') {
return 3
} else if (check == 'D' || check == 'M' || check == 'U') {
return 4
} else if (check == 'E' || check == 'N' || check == 'V') {
return 5
} else if (check == 'F' || check == 'W') {
return 6
} else if (check == 'G' || check == 'P' || check == 'X') {
return 7
} else if (check == 'H' || check == 'Y') {
return 8
} else if (check == 'R' || check == 'Z') {
return 9
} else if (Integer.valueOf(Character.getNumericValue(check)) != null) { //hacky but works
return Character.getNumericValue(check)
}
return -1
}
}
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class VinValidatorTest {
static final List<String> VALID_VINS = Arrays.asList(
"TRUWT28N011046197",
"2G1WD57C491198247",
"JH4DA3350GS005185",
"5TFUM5F18AX006026",
"1G1JC524417418958",
"JH4DB8580RS000024",
"1FTSX21P05EC23578",
"1M8GDM9AXKP042788"
);
static final List<String> INVALID_VINS_CHECKSUM_WRONG = Arrays.asList(
"TRUWT28N611046197",
"1M8GDM9AZKP042788"
);
static final List<String> INVALID_VINS_NOT17_CHAR = Arrays.asList(
"sagas",
"adsrvdsv",
"12345",
"1FTSX"
);
static final List<String> INVALID_VINS_EMPTY_VIN = Arrays.asList(
null,
"",
" ",
" "
);
static final List<String> INVALID_VINS_ILLEGAL_CHARS = Arrays.asList(
"IIIIIIIIIIIIIIIII",
"OOOOOOOOOOOOOOOOO",
"QQQQQQQQQQQQQQQQQ"
);
@Test
void givenAListOfValidVins_WhenTheCheckIsDone_ThenAnErrorShouldNOTBeThrown() {
VALID_VINS.forEach(
vin -> assertThat(VinValidator.validate(vin)).isEmpty()
);
}
@Test
void spacesAndDashesAreCleanedFromVins() {
String vinWithSpaces = "TRUW T28N01 104 6197";
String vinWithDashes = "2G1-WD57C49-1198247";
assertThat(VinValidator.validate(vinWithSpaces)).isEmpty();
assertThat(VinValidator.validate(vinWithDashes)).isEmpty();
}
@Test
void lowerCaseAreTransformedToUpperCase() {
String vinWithLowerCaseCharacters = "truwT28N011046197";
assertThat(VinValidator.validate(vinWithLowerCaseCharacters)).isEmpty();
}
@Test
void givenAListOfInValidVinsWithout17Char_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() {
INVALID_VINS_NOT17_CHAR.forEach(
vin -> assertThat(VinValidator.validate(vin))
.hasValueSatisfying(s ->
assertThat(s.errorMessage).contains("VIN number must be 17 characters")
)
);
}
@Test
void givenAListOfInValidVinsWithWrongChecksum_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() {
INVALID_VINS_CHECKSUM_WRONG.forEach(
vin -> assertThat(VinValidator.validate(vin))
.hasValueSatisfying(s ->
assertThat(s.errorMessage).contains("Illegal Vin, checksum does not match")
)
);
}
@Test
void givenAListOfInValidVinsThatAreEmpty_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() {
INVALID_VINS_EMPTY_VIN.forEach(
vin -> assertThat(VinValidator.validate(vin))
.hasValueSatisfying(s ->
assertThat(s.errorMessage).contains("VIN is mandatory")
)
);
}
@Test
void givenAListOfInValidVinsWithoutIlligalChar_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() {
INVALID_VINS_ILLEGAL_CHARS.forEach(
vin -> assertThat(VinValidator.validate(vin))
.hasValueSatisfying(s ->
assertThat(s.errorMessage).contains("Illegal character")
)
);
}
@Test
void givenASpecialValue_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() {
assertThat(VinValidator.validate("ččččččččččččččččč"))
.hasValueSatisfying(s ->
assertThat(s.errorMessage).contains("Illegal character")
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment