-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
206 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.persiantools4j; | ||
|
||
import com.persiantools4j.exception.ValidationException; | ||
|
||
public interface Validator<T> { | ||
|
||
boolean isValid(T t); | ||
|
||
void validate(T t) throws ValidationException; | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/persiantools4j/exception/ValidationException.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,9 @@ | ||
package com.persiantools4j.exception; | ||
|
||
public class ValidationException extends IllegalArgumentException { | ||
|
||
public ValidationException(String s) { | ||
super(s); | ||
} | ||
|
||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/persiantools4j/nationalid/service/NationalIdService.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,7 @@ | ||
package com.persiantools4j.nationalid.service; | ||
|
||
import com.persiantools4j.Validator; | ||
|
||
public interface NationalIdService extends Validator<String> { | ||
|
||
} |
67 changes: 67 additions & 0 deletions
67
src/main/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImpl.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,67 @@ | ||
package com.persiantools4j.nationalid.service.impl; | ||
|
||
import com.persiantools4j.exception.ValidationException; | ||
import com.persiantools4j.nationalid.service.NationalIdService; | ||
import com.persiantools4j.utils.NumberUtils; | ||
|
||
import java.util.function.BiFunction; | ||
import java.util.stream.IntStream; | ||
|
||
public class NationalIdServiceImpl implements NationalIdService { | ||
|
||
public static volatile NationalIdServiceImpl instance; | ||
|
||
private NationalIdServiceImpl() { | ||
|
||
} | ||
|
||
public static NationalIdServiceImpl getInstance() { | ||
if (instance == null) { | ||
synchronized (NationalIdServiceImpl.class) { | ||
if (instance == null) { | ||
instance = new NationalIdServiceImpl(); | ||
} | ||
} | ||
} | ||
return instance; | ||
} | ||
|
||
@Override | ||
public boolean isValid(String nationalId) { | ||
try { | ||
validate(nationalId); | ||
} catch (ValidationException e) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public void validate(String nationalId) { | ||
validateNationalIdFormat(nationalId); | ||
int controlDigit = NumberUtils.getNumericValue(nationalId, nationalId.length() - 1); | ||
BiFunction<Integer, Integer, Integer> digitCalculator = (partialResult, index) -> { | ||
int digit = NumberUtils.getNumericValue(nationalId, index); | ||
return partialResult + digit * (10 - index); | ||
}; | ||
int sum = IntStream.range(0, 9) | ||
.boxed() | ||
.reduce(0, digitCalculator, Integer::sum); | ||
int remainder = sum % 11; | ||
boolean remainderLessThanTwo = (remainder < 2) && (controlDigit == remainder); | ||
boolean remainderEqualAndMoreThanTwo = (remainder >= 2) && (remainder + controlDigit == 11); | ||
if (!remainderLessThanTwo && !remainderEqualAndMoreThanTwo) { | ||
throw new ValidationException("Invalid NationalId: " + nationalId); | ||
} | ||
} | ||
|
||
private void validateNationalIdFormat(String nationalId) { | ||
if (nationalId == null) { | ||
throw new ValidationException("National ID is null"); | ||
} | ||
if (!nationalId.matches("^\\d{10}$") || nationalId.matches("^(\\d)\\1{9}$")) { | ||
throw new ValidationException("Invalid National ID format: " + nationalId); | ||
} | ||
} | ||
|
||
} |
112 changes: 112 additions & 0 deletions
112
src/test/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImplTest.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,112 @@ | ||
package com.persiantools4j.nationalid.service.impl; | ||
|
||
import com.persiantools4j.exception.ValidationException; | ||
import com.persiantools4j.nationalid.service.NationalIdService; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.util.stream.Stream; | ||
|
||
import static org.assertj.core.api.Assertions.assertThatCode; | ||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; | ||
|
||
@DisplayName("National ID service") | ||
class NationalIdServiceImplTest { | ||
|
||
private NationalIdService nationalIdService; | ||
|
||
private static Stream<Arguments> validNationalIdCases() { | ||
return Stream.of( | ||
Arguments.of("6104038931"), | ||
Arguments.of("3111001008"), | ||
Arguments.of("3413857606"), | ||
Arguments.of("9731001018"), | ||
Arguments.of("3520082780") | ||
); | ||
} | ||
|
||
private static Stream<Arguments> invalidNationalIdCases() { | ||
return Stream.of( | ||
Arguments.of(""), | ||
Arguments.of((Object) null), | ||
Arguments.of("6104038932"), | ||
Arguments.of("3111001003"), | ||
Arguments.of("3413857604"), | ||
Arguments.of("9731001011"), | ||
Arguments.of("3520082782"), | ||
Arguments.of("0000000000"), | ||
Arguments.of("1111111111"), | ||
Arguments.of("2222222222"), | ||
Arguments.of("3333333333"), | ||
Arguments.of("4444444444"), | ||
Arguments.of("5555555555"), | ||
Arguments.of("6666666666"), | ||
Arguments.of("7777777777"), | ||
Arguments.of("8888888888"), | ||
Arguments.of("9999999999") | ||
); | ||
} | ||
|
||
@BeforeEach | ||
void setUp() { | ||
nationalIdService = NationalIdServiceImpl.getInstance(); | ||
} | ||
|
||
@Test | ||
@DisplayName("Get instance test") | ||
void testGetInstance() { | ||
NationalIdService firstNationalIdService = NationalIdServiceImpl.getInstance(); | ||
assertThat(firstNationalIdService).isNotNull(); | ||
NationalIdService secondNationalIdService = NationalIdServiceImpl.getInstance(); | ||
assertThat(secondNationalIdService).isNotNull(); | ||
assertThat(firstNationalIdService).isSameAs(secondNationalIdService); | ||
} | ||
|
||
@Test | ||
@DisplayName("Get instance thread-safe test") | ||
void testGetInstanceThreadSafe() throws InterruptedException { | ||
final NationalIdService[] nationalIdServices = new NationalIdService[2]; | ||
Thread firstThread = new Thread(() -> nationalIdServices[0] = NationalIdServiceImpl.getInstance()); | ||
Thread secondThread = new Thread(() -> nationalIdServices[1] = NationalIdServiceImpl.getInstance()); | ||
firstThread.start(); | ||
secondThread.start(); | ||
firstThread.join(); | ||
secondThread.join(); | ||
assertThat(nationalIdServices[0]).isNotNull(); | ||
assertThat(nationalIdServices[1]).isNotNull(); | ||
assertThat(nationalIdServices[0]).isSameAs(nationalIdServices[1]); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("validNationalIdCases") | ||
@DisplayName("Valid National ID test") | ||
void testValidNationalId(String nationalId) { | ||
assertThat(nationalIdService.isValid(nationalId)).isTrue(); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("invalidNationalIdCases") | ||
@DisplayName("Invalid National ID test") | ||
void testInvalidNationalId(String nationalId) { | ||
assertThat(nationalIdService.isValid(nationalId)).isFalse(); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("validNationalIdCases") | ||
@DisplayName("National ID validation test") | ||
void testValidateNationalId(String nationalId) { | ||
assertThatCode(() -> nationalIdService.validate(nationalId)).doesNotThrowAnyException(); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("invalidNationalIdCases") | ||
@DisplayName("Exceptional validation National ID test") | ||
void testExceptionValidateNationalId(String nationalId) { | ||
assertThatThrownBy(() -> nationalIdService.validate(nationalId)).isInstanceOf(ValidationException.class); | ||
} | ||
} |