From 626c9c9477a46f1d3d6c556a10b68fa7fde64f47 Mon Sep 17 00:00:00 2001 From: Scott Leberknight <174812+sleberknight@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:15:00 -0400 Subject: [PATCH] Add emptyArray and newArray to KiwiReflection2 (#276) These methods allow you to easily create an array of a given type without needing to cast. One creates an empty array while the other creates an array with a given length, and which contains only null values. The javadocs and initial version of the tests were "written" by ChatGPT. I modified them slightly, and added a few more tests. For example, a ValueSource can be used instead of a MethodSource when there is only one test parameter. I also had to slightly correct the text in the throws explanations in the javadoc, plus I added additional text including the "see" references and the implNote text. --- .../beta/reflect/KiwiReflection2.java | 34 ++++++++ .../beta/reflect/KiwiReflection2Test.java | 86 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/src/main/java/org/kiwiproject/beta/reflect/KiwiReflection2.java b/src/main/java/org/kiwiproject/beta/reflect/KiwiReflection2.java index 4648c28..06db1bc 100644 --- a/src/main/java/org/kiwiproject/beta/reflect/KiwiReflection2.java +++ b/src/main/java/org/kiwiproject/beta/reflect/KiwiReflection2.java @@ -1,11 +1,13 @@ package org.kiwiproject.beta.reflect; +import static com.google.common.base.Preconditions.checkArgument; import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull; import com.google.common.annotations.Beta; import lombok.experimental.UtilityClass; import org.checkerframework.checker.nullness.qual.NonNull; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Modifier; @@ -190,4 +192,36 @@ public static TypeInfo typeInformationOf(@NonNull Type type) { return TypeInfo.ofType(type); } + /** + * Creates an empty array of the specified type. + * + * @param the type parameter representing the component type of the array + * @param type the class object representing the component type of the array + * @return an empty array of the specified type + * @throws IllegalArgumentException if type is null or is {@link Void#TYPE} + * @see Array#newInstance(Class, int) + * @implNote This method exists because {@link Array#newInstance(Class, int)} returns Object and thus + * requires a cast. Using this method, code can be a little cleaner without a cast. + */ + public static T[] emptyArray(Class type) { + return newArray(type, 0); + } + + /** + * Creates a new array of the specified type and length. All values in the array are null. + * + * @param the type parameter representing the component type of the array + * @param type the class object representing the component type of the array + * @param length the length of the new array + * @return a new array of the specified type and length + * @throws IllegalArgumentException if type is null or is {@link Void#TYPE}, or length is negative + * @see Array#newInstance(Class, int) + * @implNote This method exists because {@link Array#newInstance(Class, int)} returns Object and thus + * requires a cast. Using this method, code can be a little cleaner without a cast. */ + @SuppressWarnings("unchecked") + public static T[] newArray(Class type, int length) { + checkArgumentNotNull(type); + checkArgument(length >= 0, "value must be positive or zero"); + return (T[]) Array.newInstance(type, length); + } } diff --git a/src/test/java/org/kiwiproject/beta/reflect/KiwiReflection2Test.java b/src/test/java/org/kiwiproject/beta/reflect/KiwiReflection2Test.java index 9ad3b48..43dc05e 100644 --- a/src/test/java/org/kiwiproject/beta/reflect/KiwiReflection2Test.java +++ b/src/test/java/org/kiwiproject/beta/reflect/KiwiReflection2Test.java @@ -6,11 +6,15 @@ import static org.junit.jupiter.api.Assertions.assertAll; import lombok.Value; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; 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.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.kiwiproject.beta.annotation.AccessedViaReflection; import org.kiwiproject.beta.reflect.KiwiReflection2.JavaAccessModifier; @@ -18,6 +22,7 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Map; +import java.util.stream.Stream; @DisplayName("KiwiReflection2") class KiwiReflection2Test { @@ -374,4 +379,85 @@ static class Raw { List rawList; Map rawMap; } + + @Test + void emptyArray_withNullType_ShouldThrowIllegalArgumentException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> KiwiReflection2.emptyArray(null)); + } + + // This exists only to have the concrete type declared (the parameterized method below tests multiple generic types) + @Test + void emptyArray_shouldReturnEmptyArray() { + Integer[] result = KiwiReflection2.emptyArray(Integer.class); + + assertThat(result).isEmpty(); + } + + @ParameterizedTest + @ValueSource(classes = {Integer.class, String.class, Boolean.class}) + void emptyArray_shouldReturnEmptyArray(Class type) { + T[] result = KiwiReflection2.emptyArray(type); + + assertThat(result).isEmpty(); + } + + @Test + void newArray_withNullTypeAndValidLength_ShouldThrowIllegalArgumentException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> KiwiReflection2.newArray(null, 10)); + } + + // This exists only to have the concrete type declared (the parameterized method below tests multiple generic types) + @Test + void newArray_shouldReturnArrayWithSpecifiedLength() { + Long[] result = KiwiReflection2.newArray(Long.class, 5); + + assertThat(result).hasSize(5).containsOnlyNulls(); + } + + @Test + void newArray_shouldReturnArrayWithSpecifiedLength_OfZero() { + String[] result = KiwiReflection2.newArray(String.class, 0); + + assertThat(result).isEmpty(); + } + + @ParameterizedTest + @MethodSource("newArrayTypeAndLengthProvider") + void newArray_shouldReturnArrayWithSpecifiedLength(Class type, int length) { + T[] result = KiwiReflection2.newArray(type, length); + + assertThat(result).hasSize(length).containsOnlyNulls(); + } + + // This exists only to have the concrete type declared (the parameterized method below tests multiple generic types) + @Test + void newArray_withNegativeLength_shouldThrowIllegalArgumentException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> KiwiReflection2.newArray(Double.class, -1)); + } + + @ParameterizedTest + @MethodSource("newArrayNegativeLengthProvider") + void newArray_withNegativeLength_shouldThrowIllegalArgumentException(Class type, int length) { + assertThatIllegalArgumentException() + .isThrownBy(() -> KiwiReflection2.newArray(type, length)); + } + + static Stream newArrayTypeAndLengthProvider() { + return Stream.of( + Arguments.of(Integer.class, 5), + Arguments.of(String.class, 10), + Arguments.of(Boolean.class, 25) + ); + } + + static Stream newArrayNegativeLengthProvider() { + return Stream.of( + Arguments.of(Double.class, -1), + Arguments.of(Character.class, -5), + Arguments.of(String.class, -42) + ); + } }