diff --git a/src/main/java/org/kiwiproject/base/KiwiPreconditions.java b/src/main/java/org/kiwiproject/base/KiwiPreconditions.java
index df0e9e49..0dc75019 100644
--- a/src/main/java/org/kiwiproject/base/KiwiPreconditions.java
+++ b/src/main/java/org/kiwiproject/base/KiwiPreconditions.java
@@ -29,8 +29,8 @@
* If you're looking for preconditions related to validating arguments using Jakarta Beans Validation, they
* are in {@link org.kiwiproject.validation.KiwiValidations KiwiValidations}.
*
- * @implNote Several methods in this class use Lombok's {@link lombok.SneakyThrows} so that they do not need to declare
- * that they throw exceptions of type T, for the case that T is a checked exception. Read more details about
+ * @implNote Several methods in this class use Lombok {@link lombok.SneakyThrows} so that they do not need to declare
+ * that they throw {@code Exception}s of type T, for the case that T is a checked exception. Read more details about
* how this works in {@link lombok.SneakyThrows}. Most notably, this should give you more insight into how the JVM (versus
* Java the language) actually work: "The JVM does not check for the consistency of the checked exception system;
* javac does, and this annotation lets you opt out of its mechanism."
@@ -44,12 +44,12 @@ public class KiwiPreconditions {
/**
* Ensures the truth of an expression involving one or more parameters to the calling method.
*
- * Throws an exception of type T if {@code expression} is false.
+ * Throws an {@code Exception} of type T if {@code expression} is false.
*
* @param expression a boolean expression
* @param exceptionType the type of exception to be thrown if {@code expression} is false
* @param the type of exception
- * @implNote This uses Lombok's {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
+ * @implNote This uses Lombok {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
*/
@SneakyThrows(Throwable.class)
public static void checkArgument(boolean expression, Class exceptionType) {
@@ -61,13 +61,13 @@ public static void checkArgument(boolean expression, Class
/**
* Ensures the truth of an expression involving one or more parameters to the calling method.
*
- * Throws an exception of type T if {@code expression} is false.
+ * Throws an {@code Exception} of type T if {@code expression} is false.
*
* @param expression a boolean expression
* @param exceptionType the type of exception to be thrown if {@code expression} is false
* @param errorMessage the exception message to use if the check fails
* @param the type of exception
- * @implNote This uses Lombok's {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
+ * @implNote This uses Lombok {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
*/
@SneakyThrows(Throwable.class)
public static void checkArgument(boolean expression,
@@ -82,7 +82,7 @@ public static void checkArgument(boolean expression,
/**
* Ensures the truth of an expression involving one or more parameters to the calling method.
*
- * Throws an exception of type T if {@code expression} is false.
+ * Throws an {@code Exception} of type T if {@code expression} is false.
*
* @param expression a boolean expression
* @param exceptionType the type of exception to be thrown if {@code expression} is false
@@ -93,7 +93,7 @@ public static void checkArgument(boolean expression,
* @param the type of exception
* @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
* {@code errorMessageArgs} is null (don't let this happen)
- * @implNote This uses Lombok's {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
+ * @implNote This uses Lombok {@link lombok.SneakyThrows} to throw any checked exceptions without declaring them.
*/
@SneakyThrows(Throwable.class)
public static void checkArgument(boolean expression,
@@ -152,7 +152,7 @@ public static String requireNotBlank(String value, String errorMessageTemplate,
/**
* Ensures that an object reference passed as a parameter to the calling method is not null, throwing
- * an {@link IllegalArgumentException} if null or returning the (non null) reference otherwise.
+ * an {@link IllegalArgumentException} if null or returning the (non-null) reference otherwise.
*
* @param reference an object reference
* @param the type of object
@@ -165,7 +165,7 @@ public static T requireNotNull(T reference) {
/**
* Ensures that an object reference passed as a parameter to the calling method is not null, throwing
- * an {@link IllegalArgumentException} if null or returning the (non null) reference otherwise.
+ * an {@link IllegalArgumentException} if null or returning the (non-null) reference otherwise.
*
* @param reference an object reference
* @param errorMessageTemplate a template for the exception message should the check fail, according to how
@@ -421,12 +421,6 @@ public static void checkArgumentNotEmpty(Map map,
}
}
- private static IllegalArgumentException newIllegalArgumentException(String errorMessageTemplate,
- Object... errorMessageArgs) {
- var errorMessage = format(errorMessageTemplate, errorMessageArgs);
- return new IllegalArgumentException(errorMessage);
- }
-
/**
* Ensures that a collection of items has an even count, throwing an {@link IllegalArgumentException} if
* items is null or there is an odd number of items.
@@ -954,4 +948,105 @@ public static int requireValidNonZeroPort(int port, String errorMessageTemplate,
return port;
}
+ /**
+ * Ensures the argument has the expected type.
+ *
+ * @param argument the argument to check
+ * @param requiredType the type that the argument is required to be
+ * @param the class of the required type
+ */
+ public static void checkArgumentInstanceOf(T argument, Class> requiredType) {
+ Preconditions.checkArgument(isInstanceOf(argument, requiredType));
+ }
+
+ /**
+ * Ensures the argument has the expected type.
+ *
+ * @param argument the argument to check
+ * @param requiredType the type that the argument is required to be
+ * @param errorMessage the error message to put in the exception if the argument is not the required type
+ * @param the class of the required type
+ */
+ public static void checkArgumentInstanceOf(T argument, Class> requiredType, String errorMessage) {
+ Preconditions.checkArgument(isInstanceOf(argument, requiredType), errorMessage);
+ }
+
+ /**
+ * Ensures the argument has the expected type.
+ *
+ * @param argument the argument to check
+ * @param requiredType the type that the argument is required to be
+ * @param errorMessageTemplate the error message template to use in the exception if the argument is not the
+ * required type, according to how {@link KiwiStrings#format(String, Object...)}
+ * handles placeholders
+ * @param errorMessageArgs the arguments to populate into the error message template
+ * @param the class of the required type
+ */
+ public static void checkArgumentInstanceOf(T argument,
+ Class> requiredType,
+ String errorMessageTemplate,
+ Object... errorMessageArgs) {
+
+ if (isNotInstanceOf(argument, requiredType)) {
+ throw newIllegalArgumentException(errorMessageTemplate, errorMessageArgs);
+ }
+ }
+
+ /**
+ * Ensures the argument type is not the restricted type.
+ *
+ * @param argument the argument to check
+ * @param restrictedType the type that the argument must not be
+ * @param the class of the restricted type
+ */
+ public static void checkArgumentNotInstanceOf(T argument, Class> restrictedType) {
+ Preconditions.checkArgument(isNotInstanceOf(argument, restrictedType));
+ }
+
+ /**
+ * Ensures the argument type is not the restricted type.
+ *
+ * @param argument the argument to check
+ * @param restrictedType the type that the argument must not be
+ * @param errorMessage the error message to put in the exception if the argument is of the restricted type
+ * @param the class of the restricted type
+ */
+ public static void checkArgumentNotInstanceOf(T argument, Class> restrictedType, String errorMessage) {
+ Preconditions.checkArgument(isNotInstanceOf(argument, restrictedType), errorMessage);
+ }
+
+ /**
+ * Ensures the argument type is not the restricted type.
+ *
+ * @param argument the argument to check
+ * @param restrictedType the type that the argument must not be
+ * @param errorMessageTemplate the error message to use in the exception if the argument is of the restricted type,
+ * according to how {@link KiwiStrings#format(String, Object...)} handles placeholders
+ * @param errorMessageArgs the arguments to populate into the error message template
+ * @param the class of the restricted type
+ */
+ public static void checkArgumentNotInstanceOf(T argument,
+ Class> restrictedType,
+ String errorMessageTemplate,
+ Object... errorMessageArgs) {
+
+ if (isInstanceOf(argument, restrictedType)) {
+ throw newIllegalArgumentException(errorMessageTemplate, errorMessageArgs);
+ }
+ }
+
+ private static boolean isInstanceOf(T argument, Class> requiredType) {
+ return nonNull(argument) && requiredType.isAssignableFrom(argument.getClass());
+ }
+
+ private static boolean isNotInstanceOf(T argument, Class> restrictedType) {
+ return isNull(argument) || !restrictedType.isAssignableFrom(argument.getClass());
+ }
+
+ private static IllegalArgumentException newIllegalArgumentException(String errorMessageTemplate,
+ Object... errorMessageArgs) {
+ var errorMessage = format(errorMessageTemplate, errorMessageArgs);
+ return new IllegalArgumentException(errorMessage);
+ }
+
}
diff --git a/src/test/java/org/kiwiproject/base/KiwiPreconditionsTest.java b/src/test/java/org/kiwiproject/base/KiwiPreconditionsTest.java
index 4d3cbaaa..5dd3e6bc 100644
--- a/src/test/java/org/kiwiproject/base/KiwiPreconditionsTest.java
+++ b/src/test/java/org/kiwiproject/base/KiwiPreconditionsTest.java
@@ -14,6 +14,7 @@
import static org.kiwiproject.base.KiwiPreconditions.requireNotNullElse;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNullElseGet;
+import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
import org.junit.jupiter.api.DisplayName;
@@ -21,16 +22,25 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import org.kiwiproject.util.BlankStringArgumentsProvider;
import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Stream;
@DisplayName("KiwiPreconditions")
@ExtendWith(SoftAssertionsExtension.class)
@@ -104,6 +114,7 @@ void testCheckArgument_WhenNoErrorMessage(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeCheckedException.class)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeCheckedException.class)))
.isExactlyInstanceOf(SomeCheckedException.class)
@@ -113,6 +124,7 @@ void testCheckArgument_WhenNoErrorMessage(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeRuntimeException.class)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeRuntimeException.class)))
.isExactlyInstanceOf(SomeRuntimeException.class)
@@ -127,6 +139,7 @@ void testCheckArgument_WhenHasMessageConstant(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeCheckedException.class, message)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeCheckedException.class, message)))
.isExactlyInstanceOf(SomeCheckedException.class)
@@ -136,6 +149,7 @@ void testCheckArgument_WhenHasMessageConstant(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeRuntimeException.class, message)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeRuntimeException.class, message)))
.isExactlyInstanceOf(SomeRuntimeException.class)
@@ -151,6 +165,7 @@ void testCheckArgument_WhenHasMessageTemplateWithArgs(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeCheckedException.class, template, args)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeCheckedException.class, template, args)))
.isExactlyInstanceOf(SomeCheckedException.class)
@@ -160,6 +175,7 @@ void testCheckArgument_WhenHasMessageTemplateWithArgs(SoftAssertions softly) {
KiwiPreconditions.checkArgument(true, SomeRuntimeException.class, template, args)))
.isNull();
+ //noinspection DataFlowIssue
softly.assertThat(catchThrowable(() ->
KiwiPreconditions.checkArgument(false, SomeRuntimeException.class, template, args)))
.isExactlyInstanceOf(SomeRuntimeException.class)
@@ -1165,4 +1181,155 @@ void shouldNotThrowException_WithCustomMessageTemplate_WhenPort_IsValid() {
}
}
+
+ @Nested
+ class CheckArgumentInstanceOf {
+
+ @Nested
+ class WithNoMessage {
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldNotThrow_WhenArgumentIsExpectedType(Object argument, Class> expectedType) {
+ assertThatCode(() -> KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldThrow_WhenArgumentIsNotExpectedType(Object argument, Class> expectedType) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType));
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldNotThrow_WhenArgumentIsExpectedType(Object argument, Class> expectedType) {
+ assertThatCode(() ->
+ KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType, "not expected type"))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldThrow_WhenArgumentIsNotExpectedType(Object argument, Class> expectedType) {
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType, "not expected type"))
+ .withMessage("not expected type");
+ }
+ }
+
+ @Nested
+ class WithMessageTemplateAndArguments {
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldNotThrow_WhenArgumentIsExpectedType(Object argument, Class> expectedType) {
+ assertThatCode(() ->
+ KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType, "arg not expected type {}", expectedType))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldThrow_WhenArgumentIsNotExpectedType(Object argument, Class> expectedType) {
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ KiwiPreconditions.checkArgumentInstanceOf(argument, expectedType, "arg not expected {}", expectedType))
+ .withMessage("arg not expected %s", expectedType);
+ }
+ }
+ }
+
+ @Nested
+ class CheckArgumentNotInstanceOf {
+
+ @Nested
+ class WithNoMessage {
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldNotThrow_WhenIsNotInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatCode(() -> KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldThrow_WhenArgumentIsInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType));
+ }
+ }
+
+ @Nested
+ class WithMessage {
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldNotThrow_WhenIsNotInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatCode(() ->
+ KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType, "has restricted type"))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldThrow_WhenArgumentIsInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType, "has restricted type"))
+ .withMessage("has restricted type");
+ }
+ }
+
+ @Nested
+ class WithMessageTemplateAndArguments {
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#notInstanceOfArguments")
+ void shouldNotThrow_WhenIsNotInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatCode(() ->
+ KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType, "has restricted type {}", restrictedType))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.kiwiproject.base.KiwiPreconditionsTest#instanceOfArguments")
+ void shouldThrow_WhenArgumentIsInstanceOfRestrictedType(Object argument, Class> restrictedType) {
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ KiwiPreconditions.checkArgumentNotInstanceOf(argument, restrictedType, "has restricted type {}", restrictedType))
+ .withMessage("has restricted type %s", restrictedType);
+ }
+ }
+ }
+
+ static Stream instanceOfArguments() {
+ return Stream.of(
+ Arguments.of(RandomStringUtils.random(5), Object.class),
+ Arguments.of(RandomStringUtils.random(15), String.class),
+ Arguments.of(RandomStringUtils.random(10), CharSequence.class),
+ Arguments.of(random().nextLong(), Long.class),
+ Arguments.of(random().nextInt(), Integer.class),
+ Arguments.of(random().nextInt(), Number.class),
+ Arguments.of(new java.sql.Timestamp(System.currentTimeMillis()), Date.class),
+ Arguments.of(Instant.now(), Instant.class),
+ Arguments.of(LocalDateTime.now(), LocalDateTime.class)
+ );
+ }
+
+ static Stream notInstanceOfArguments() {
+ return Stream.of(
+ Arguments.of(RandomStringUtils.random(15), Long.class),
+ Arguments.of(random().nextLong(), String.class),
+ Arguments.of(random().nextInt(), Collection.class),
+ Arguments.of(Instant.now(), Date.class),
+ Arguments.of(LocalDateTime.now(), ZonedDateTime.class)
+ );
+ }
+
+ private static ThreadLocalRandom random() {
+ return ThreadLocalRandom.current();
+ }
}