From 4926f549775520ca8aef22302b13e7afa8c80cad Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 29 Mar 2021 13:53:12 +0200 Subject: [PATCH] Polishing. Refactor ArrayUtil into a proper utility class providing toPrimitive and toObject methods for each primitive type. Add boolean support. Move convertToObjectArray to BasicJdbcConverter as its placement in ArrayUtils creates a certain amount of ambiguity over its actual purpose. Create unit test. See #945 Original pull request: #949. --- .../data/jdbc/core/convert/ArrayUtil.java | 137 ------ .../data/jdbc/core/convert/ArrayUtils.java | 464 ++++++++++++++++++ .../jdbc/core/convert/BasicJdbcConverter.java | 41 +- .../core/convert/ArrayUtilsUnitTests.java | 55 +++ 4 files changed, 558 insertions(+), 139 deletions(-) delete mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtils.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/ArrayUtilsUnitTests.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java deleted file mode 100644 index ef590a12..00000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2019-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.core.convert; - -import lombok.experimental.UtilityClass; - -/** - * A collection of utility methods for dealing with arrays. - * - * @author Jens Schauder - * @since 1.1 - */ -@UtilityClass -class ArrayUtil { - - /** - * Converts an {@code Byte[]} into a {@code byte[]}. - * - * @param byteArray the array to be converted. Must not be {@literal null}. - * @return a {@code byte[]} of same size with the unboxed values of the input array. Guaranteed to be not - * {@literal null}. - */ - static byte[] toPrimitiveByteArray(Byte[] byteArray) { - - byte[] bytes = new byte[byteArray.length]; - for (int i = 0; i < byteArray.length; i++) { - bytes[i] = byteArray[i]; - } - return bytes; - } - - static Byte[] toObjectArray(byte[] primitiveArray) { - - Byte[] objects = new Byte[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Short[] toObjectArray(short[] primitiveArray) { - - Short[] objects = new Short[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Character[] toObjectArray(char[] primitiveArray) { - - Character[] objects = new Character[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Integer[] toObjectArray(int[] primitiveArray) { - - Integer[] objects = new Integer[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Long[] toObjectArray(long[] primitiveArray) { - - Long[] objects = new Long[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Float[] toObjectArray(float[] primitiveArray) { - - Float[] objects = new Float[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Double[] toObjectArray(double[] primitiveArray) { - - Double[] objects = new Double[primitiveArray.length]; - for (int i = 0; i < primitiveArray.length; i++) { - objects[i] = primitiveArray[i]; - } - return objects; - } - - static Object[] convertToObjectArray(Object unknownArray) { - - Class componentType = unknownArray.getClass().getComponentType(); - - if (componentType.isPrimitive()) { - if (componentType == byte.class) { - return toObjectArray((byte[]) unknownArray); - } - if (componentType == short.class) { - return toObjectArray((short[]) unknownArray); - } - if (componentType == char.class) { - return toObjectArray((char[]) unknownArray); - } - if (componentType == int.class) { - return toObjectArray((int[]) unknownArray); - } - if (componentType == long.class) { - return toObjectArray((long[]) unknownArray); - } - if (componentType == float.class) { - return toObjectArray((float[]) unknownArray); - } - if (componentType == double.class) { - return toObjectArray((double[]) unknownArray); - } - } - return (Object[]) unknownArray; - } -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtils.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtils.java new file mode 100644 index 00000000..315b7bd8 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtils.java @@ -0,0 +1,464 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.core.convert; + +import org.springframework.util.Assert; + +/** + * A collection of utility methods for dealing with arrays. + *

+ * Mainly for internal use within the framework. + * + * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 + */ +final class ArrayUtils { + + /** + * An empty immutable {@code boolean} array. + */ + public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + + /** + * An empty immutable {@link Boolean} array. + */ + public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = new Boolean[0]; + + /** + * An empty immutable {@code byte} array. + */ + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * An empty immutable {@link Byte} array. + */ + public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = new Byte[0]; + + /** + * An empty immutable {@code char} array. + */ + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + /** + * An empty immutable {@link Character} array. + */ + public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = new Character[0]; + + /** + * An empty immutable {@code double} array. + */ + public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + + /** + * An empty immutable {@code Double} array. + */ + public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0]; + + /** + * An empty immutable {@code float} array. + */ + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + + /** + * An empty immutable {@code Float} array. + */ + public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = new Float[0]; + + /** + * An empty immutable {@code int} array. + */ + public static final int[] EMPTY_INT_ARRAY = new int[0]; + + /** + * An empty immutable {@link Integer} array. + */ + public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0]; + + /** + * An empty immutable {@code long} array. + */ + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + + /** + * An empty immutable {@link Long} array. + */ + public static final Long[] EMPTY_LONG_OBJECT_ARRAY = new Long[0]; + + /** + * An empty immutable {@code short} array. + */ + public static final short[] EMPTY_SHORT_ARRAY = new short[0]; + + /** + * An empty immutable {@link Short} array. + */ + public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = new Short[0]; + + private ArrayUtils() { + } + + /** + * Converts an {@code Boolean[]} into a {@code boolean[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code boolean[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static boolean[] toPrimitive(Boolean[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + + boolean[] booleans = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + booleans[i] = array[i]; + } + + return booleans; + } + + /** + * Converts an {@code boolean[]} into a {@code Boolean[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Boolean[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Boolean[] toObject(boolean[] array) { + + if (array.length == 0) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + + Boolean[] booleans = new Boolean[array.length]; + for (int i = 0; i < array.length; i++) { + booleans[i] = array[i]; + } + + return booleans; + } + + /** + * Converts an {@code Byte[]} into a {@code byte[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code byte[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static byte[] toPrimitive(Byte[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_BYTE_ARRAY; + } + + byte[] bytes = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + bytes[i] = array[i]; + } + + return bytes; + } + + /** + * Converts an {@code byte[]} into a {@code Byte[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Byte[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Byte[] toObject(byte[] array) { + + if (array.length == 0) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + + Byte[] bytes = new Byte[array.length]; + for (int i = 0; i < array.length; i++) { + bytes[i] = array[i]; + } + + return bytes; + } + + /** + * Converts an {@code Character[]} into a {@code char[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code char[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static char[] toPrimitive(Character[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_CHAR_ARRAY; + } + + char[] chars = new char[array.length]; + for (int i = 0; i < array.length; i++) { + chars[i] = array[i]; + } + + return chars; + } + + /** + * Converts an {@code char[]} into a {@code Character[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Character[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Character[] toObject(char[] array) { + + if (array.length == 0) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + + Character[] objects = new Character[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + return objects; + } + + /** + * Converts an {@code Double[]} into a {@code double[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code double[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static double[] toPrimitive(Double[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_DOUBLE_ARRAY; + } + + double[] doubles = new double[array.length]; + for (int i = 0; i < array.length; i++) { + doubles[i] = array[i]; + } + + return doubles; + } + + /** + * Converts an {@code double[]} into a {@code Double[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Double[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Double[] toObject(double[] array) { + + if (array.length == 0) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + + Double[] objects = new Double[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + + return objects; + } + + /** + * Converts an {@code Float[]} into a {@code float[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code float[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static float[] toPrimitive(Float[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_FLOAT_ARRAY; + } + + float[] floats = new float[array.length]; + for (int i = 0; i < array.length; i++) { + floats[i] = array[i]; + } + + return floats; + } + + /** + * Converts an {@code float[]} into a {@code Float[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Float[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Float[] toObject(float[] array) { + + if (array.length == 0) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + + Float[] objects = new Float[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + + return objects; + } + + /** + * Converts an {@code Integer[]} into a {@code int[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code int[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static int[] toPrimitive(Integer[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_INT_ARRAY; + } + + int[] ints = new int[array.length]; + for (int i = 0; i < array.length; i++) { + ints[i] = array[i]; + } + + return ints; + } + + /** + * Converts an {@code int[]} into a {@code Integer[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Integer[]} of same size with the boxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Integer[] toObject(int[] array) { + + if (array.length == 0) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + + Integer[] objects = new Integer[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + + return objects; + } + + /** + * Converts an {@code Long[]} into a {@code long[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code long[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static long[] toPrimitive(Long[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_LONG_ARRAY; + } + + long[] longs = new long[array.length]; + for (int i = 0; i < array.length; i++) { + longs[i] = array[i]; + } + + return longs; + } + + /** + * Converts an {@code long[]} into a {@code Long[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Long[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Long[] toObject(long[] array) { + + if (array.length == 0) { + return EMPTY_LONG_OBJECT_ARRAY; + } + + Long[] objects = new Long[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + return objects; + } + + /** + * Converts an {@code Short[]} into a {@code short[]}. + * + * @param array the array to be converted. Must not be {@literal null} and must not contain {@literal null} elements. + * @return a {@code short[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static short[] toPrimitive(Short[] array) { + + Assert.noNullElements(array, "Array must not contain null elements"); + + if (array.length == 0) { + return EMPTY_SHORT_ARRAY; + } + + short[] shorts = new short[array.length]; + for (int i = 0; i < array.length; i++) { + shorts[i] = array[i]; + } + + return shorts; + } + + /** + * Converts an {@code short[]} into a {@code Short[]}. + * + * @param array the array to be converted. Must not be {@literal null}. + * @return a {@code Short[]} of same size with the unboxed values of the input array. Guaranteed to be not + * {@literal null}. + */ + static Short[] toObject(short[] array) { + + if (array.length == 0) { + return EMPTY_SHORT_OBJECT_ARRAY; + } + + Short[] objects = new Short[array.length]; + for (int i = 0; i < array.length; i++) { + objects[i] = array[i]; + } + + return objects; + } + +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 67bb927f..43a63d93 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -298,12 +298,12 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc Class componentType = convertedValue.getClass().getComponentType(); if (componentType != byte.class && componentType != Byte.class) { - Object[] objectArray = ArrayUtil.convertToObjectArray(convertedValue); + Object[] objectArray = requireObjectArray(convertedValue); return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY); } if (componentType == Byte.class) { - convertedValue = ArrayUtil.toPrimitiveByteArray((Byte[]) convertedValue); + convertedValue = ArrayUtils.toPrimitive((Byte[]) convertedValue); } return JdbcValue.of(convertedValue, JDBCType.BINARY); @@ -330,6 +330,43 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc return new ReadingContext(path, new ResultSetAccessor(resultSet), identifier, key).mapRow(); } + static Object[] requireObjectArray(Object source) { + + Assert.isTrue(source.getClass().isArray(), "Source object is not an array"); + + Class componentType = source.getClass().getComponentType(); + + if (componentType.isPrimitive()) { + if (componentType == boolean.class) { + return ArrayUtils.toObject((boolean[]) source); + } + if (componentType == byte.class) { + return ArrayUtils.toObject((byte[]) source); + } + if (componentType == char.class) { + return ArrayUtils.toObject((char[]) source); + } + if (componentType == double.class) { + return ArrayUtils.toObject((double[]) source); + } + if (componentType == float.class) { + return ArrayUtils.toObject((float[]) source); + } + if (componentType == int.class) { + return ArrayUtils.toObject((int[]) source); + } + if (componentType == long.class) { + return ArrayUtils.toObject((long[]) source); + } + if (componentType == short.class) { + return ArrayUtils.toObject((short[]) source); + } + + throw new IllegalArgumentException("Unsupported component type: " + componentType); + } + return (Object[]) source; + } + private class ReadingContext { private final RelationalPersistentEntity entity; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/ArrayUtilsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/ArrayUtilsUnitTests.java new file mode 100644 index 00000000..f20b8130 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/ArrayUtilsUnitTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.core.convert; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.data.Offset.offset; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link ArrayUtils}. + * + * @author Mark Paluch + */ +class ArrayUtilsUnitTests { + + @Test + void testCreatePrimitiveArray() { + + assertThat(ArrayUtils.toPrimitive(new Boolean[] { true })).isEqualTo(new boolean[] { true }); + assertThat(ArrayUtils.toPrimitive(new Byte[] { 1 })).isEqualTo(new byte[] { 1 }); + assertThat(ArrayUtils.toPrimitive(new Character[] { 'a' })).isEqualTo(new char[] { 'a' }); + assertThat(ArrayUtils.toPrimitive(new Double[] { 2.718 })).contains(new double[] { 2.718 }, offset(0.1)); + assertThat(ArrayUtils.toPrimitive(new Float[] { 3.14f })).contains(new float[] { 3.14f }, offset(0.1f)); + assertThat(ArrayUtils.toPrimitive(new Integer[] {})).isEqualTo(new int[] {}); + assertThat(ArrayUtils.toPrimitive(new Long[] { 2L, 3L })).isEqualTo(new long[] { 2, 3 }); + assertThat(ArrayUtils.toPrimitive(new Short[] { 2 })).isEqualTo(new short[] { 2 }); + } + + @Test + void testCreatePrimitiveArrayViaObjectArray() { + + assertThat(ArrayUtils.toPrimitive(new Boolean[] { true })).isEqualTo(new boolean[] { true }); + assertThat(ArrayUtils.toPrimitive(new Byte[] { 1 })).isEqualTo(new byte[] { 1 }); + assertThat(ArrayUtils.toPrimitive(new Character[] { 'a' })).isEqualTo(new char[] { 'a' }); + assertThat(ArrayUtils.toPrimitive(new Double[] { 2.718 })).contains(new double[] { 2.718 }, offset(0.1)); + assertThat(ArrayUtils.toPrimitive(new Float[] { 3.14f })).contains(new float[] { 3.14f }, offset(0.1f)); + assertThat(ArrayUtils.toPrimitive(new Integer[] {})).isEqualTo(new int[] {}); + assertThat(ArrayUtils.toPrimitive(new Long[] { 2L, 3L })).isEqualTo(new long[] { 2, 3 }); + assertThat(ArrayUtils.toPrimitive(new Short[] { 2 })).isEqualTo(new short[] { 2 }); + } +}