This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new eefe5c88b [LANG-1707] Add ArrayUtils.concat methods for concatenating
multiple arrays (#1519)
eefe5c88b is described below
commit eefe5c88bc54a164380652f8c48c375c0ed2fa31
Author: Ivan Malutin <[email protected]>
AuthorDate: Sun Jan 25 17:12:26 2026 +0300
[LANG-1707] Add ArrayUtils.concat methods for concatenating multiple arrays
(#1519)
* LANG-1707 add concat methods to ArrayUtils
* Add missing test assertions for null inputs
- Javadoc
- Use longer lines
* Add Javadoc @throws
---------
Co-authored-by: Gary Gregory <[email protected]>
---
pom.xml | 5 +
.../java/org/apache/commons/lang3/ArrayUtils.java | 286 +++++++++++++++++++++
.../apache/commons/lang3/ArrayUtilsConcatTest.java | 118 +++++++++
3 files changed, 409 insertions(+)
diff --git a/pom.xml b/pom.xml
index 42ff459fa..4d77dc751 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,11 @@
<version>5.6.0</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-inline</artifactId>
+ <version>${commons.mockito.version}</version>
+ </dependency>
<!-- For Javadoc links -->
<dependency>
<groupId>org.apache.commons</groupId>
diff --git a/src/main/java/org/apache/commons/lang3/ArrayUtils.java
b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
index 825402631..2526811ba 100644
--- a/src/main/java/org/apache/commons/lang3/ArrayUtils.java
+++ b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
@@ -9340,6 +9340,283 @@ public static String[] toStringArray(final Object[]
array, final String valueFor
return map(array, String.class, e -> Objects.toString(e,
valueForNullElements));
}
+ /**
+ * Concatenates multiple boolean arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new boolean array containing all elements from the input
arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static boolean[] concat(boolean[]... arrays) {
+ int totalLength = 0;
+ for (boolean[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final boolean[] result = new boolean[totalLength];
+ int currentPos = 0;
+ for (boolean[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple byte arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new byte array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static byte[] concat(byte[]... arrays) {
+ int totalLength = 0;
+ for (byte[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final byte[] result = new byte[totalLength];
+ int currentPos = 0;
+ for (byte[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple char arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new char array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static char[] concat(char[]... arrays) {
+ int totalLength = 0;
+ for (char[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final char[] result = new char[totalLength];
+ int currentPos = 0;
+ for (char[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple double arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new double array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static double[] concat(double[]... arrays) {
+ int totalLength = 0;
+ for (double[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final double[] result = new double[totalLength];
+ int currentPos = 0;
+ for (double[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple float arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new float array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static float[] concat(float[]... arrays) {
+ int totalLength = 0;
+ for (float[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final float[] result = new float[totalLength];
+ int currentPos = 0;
+ for (float[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple int arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new int array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static int[] concat(int[]... arrays) {
+ int totalLength = 0;
+ for (int[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final int[] result = new int[totalLength];
+ int currentPos = 0;
+ for (int[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple long arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new long array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static long[] concat(long[]... arrays) {
+ int totalLength = 0;
+ for (long[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final long[] result = new long[totalLength];
+ int currentPos = 0;
+ for (long[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple short arrays into a single array.
+ * <p>
+ * This method combines all input arrays in the order they are provided,
+ * creating a new array that contains all elements from the input arrays.
+ * The resulting array length is the sum of lengths of all non-null input
arrays.
+ * </p>
+ *
+ * @param arrays the arrays to concatenate. Can be empty, contain nulls,
+ * or be null itself (treated as empty varargs).
+ * @return a new short array containing all elements from the input arrays
+ * in the order they appear, or an empty array if no elements are
present.
+ * @throws NullPointerException if the input array of arrays is null.
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ * @since 3.21.0
+ */
+ public static short[] concat(short[]... arrays) {
+ int totalLength = 0;
+ for (short[] array : arrays) {
+ totalLength = addExact(totalLength, array);
+ }
+ final short[] result = new short[totalLength];
+ int currentPos = 0;
+ for (short[] array : arrays) {
+ if (array != null && array.length > 0) {
+ System.arraycopy(array, 0, result, currentPos, array.length);
+ currentPos += array.length;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Safely adds the length of an array to a running total, checking for
overflow.
+ *
+ * @param totalLength the current accumulated length
+ * @param array the array whose length should be added (can be {@code
null},
+ * in which case its length is considered 0)
+ * @return the new total length after adding the array's length
+ * @throws IllegalArgumentException if total arrays length exceed {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ */
+ private static int addExact(final int totalLength, final Object array) {
+ try {
+ final int length = MathBridge.addExact(totalLength,
getLength(array));
+ if (length > SAFE_MAX_ARRAY_LENGTH) {
+ throw new IllegalArgumentException("Total arrays length exceed
" + SAFE_MAX_ARRAY_LENGTH);
+ }
+ return length;
+ } catch (final ArithmeticException exception) {
+ throw new IllegalArgumentException("Total arrays length exceed " +
SAFE_MAX_ARRAY_LENGTH);
+ }
+ }
+
/**
* ArrayUtils instances should NOT be constructed in standard programming.
Instead, the class should be used as {@code ArrayUtils.clone(new int[] {2})}.
* <p>
@@ -9352,4 +9629,13 @@ public static String[] toStringArray(final Object[]
array, final String valueFor
public ArrayUtils() {
// empty
}
+
+ /**
+ * Bridge class to {@link Math} methods for testing purposes.
+ */
+ static class MathBridge {
+ static int addExact(final int a, final int b) {
+ return Math.addExact(a, b);
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsConcatTest.java
b/src/test/java/org/apache/commons/lang3/ArrayUtilsConcatTest.java
new file mode 100644
index 000000000..fdec27f08
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsConcatTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.lang3;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mockStatic;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/**
+ * Tests {@link ArrayUtils} concat methods.
+ */
+class ArrayUtilsConcatTest extends AbstractLangTest {
+
+ @Test
+ void testBooleanArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((boolean[][]) null));
+ assertArrayEquals(new boolean[] {}, ArrayUtils.concat((boolean[])
null, null));
+ assertArrayEquals(new boolean[] { true, false, true },
ArrayUtils.concat(new boolean[] { true, false, true }, null));
+ assertArrayEquals(new boolean[] { false, true, false },
ArrayUtils.concat(null, new boolean[] { false, true, false }));
+ assertArrayEquals(new boolean[] { false, true, false, false, true },
+ ArrayUtils.concat(new boolean[] { false, true, false }, new
boolean[] { false, true }));
+ }
+
+ @Test
+ void testByteArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((byte[][]) null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null,
null));
+ assertArrayEquals(new byte[] { 1, 2, 3 }, ArrayUtils.concat(new byte[]
{ 1, 2, 3 }, null));
+ assertArrayEquals(new byte[] { -3, 2, 1 }, ArrayUtils.concat(null, new
byte[] { -3, 2, 1 }));
+ assertArrayEquals(new byte[] { 1, 3, 2, 100, 7 },
ArrayUtils.concat(new byte[] { 1, 3, 2 }, new byte[] { 100, 7 }));
+ }
+
+ @Test
+ void testCharArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((char[][]) null));
+ assertArrayEquals(new char[] {}, ArrayUtils.concat((char[]) null,
null));
+ assertArrayEquals(new char[] { 'a', 'b', 'c' }, ArrayUtils.concat(new
char[] { 'a', 'b', 'c' }, null));
+ assertArrayEquals(new char[] { 'b', 'a', 'c' },
ArrayUtils.concat(null, new char[] { 'b', 'a', 'c' }));
+ assertArrayEquals(new char[] { 'a', 'b', 'c', 'q', 'w' },
ArrayUtils.concat(new char[] { 'a', 'b', 'c' }, new char[] { 'q', 'w' }));
+ }
+
+ @Test
+ void testDoubleArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((double[][]) null));
+ assertArrayEquals(new double[] {}, ArrayUtils.concat((double[]) null,
null));
+ assertArrayEquals(new double[] { 1e-300, .2e-300, 3.0e-300 },
ArrayUtils.concat(new double[] { 1e-300, .2e-300, 3.0e-300 }, null));
+ assertArrayEquals(new double[] { 3.0e-300, 1e-300, .2e-300 },
ArrayUtils.concat(null, new double[] { 3.0e-300, 1e-300, .2e-300 }));
+ assertArrayEquals(new double[] { 1e-300, .2e-300, 3.0e-300,
10.01e-300, 0.001e-300 },
+ ArrayUtils.concat(new double[] { 1e-300, .2e-300, 3.0e-300 },
new double[] { 10.01e-300, 0.001e-300 }));
+ }
+
+ @Test
+ void testExceedSafeMaxArraySize() {
+ try (MockedStatic<ArrayUtils.MathBridge> mockedStatic =
mockStatic(ArrayUtils.MathBridge.class)) {
+ mockedStatic.when(() -> ArrayUtils.MathBridge.addExact(anyInt(),
anyInt())).thenThrow(ArithmeticException.class);
+ assertThrows(IllegalArgumentException.class, () ->
ArrayUtils.concat(new int[] { 0 }, new int[] { 1 }));
+ }
+ }
+
+ @Test
+ void testFloatArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((float[][]) null));
+ assertArrayEquals(new float[] {}, ArrayUtils.concat((float[]) null,
null));
+ assertArrayEquals(new float[] { 1f, .2f, 3.0f }, ArrayUtils.concat(new
float[] { 1f, .2f, 3.0f }, null));
+ assertArrayEquals(new float[] { 3.0f, 1f, .2f },
ArrayUtils.concat(null, new float[] { 3.0f, 1f, .2f }));
+ assertArrayEquals(new float[] { 1f, .2f, 3.0f, 10.01f, 0.001f },
ArrayUtils.concat(new float[] { 1f, .2f, 3.0f }, new float[] { 10.01f, 0.001f
}));
+ }
+
+ @Test
+ void testIntArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((int[][]) null));
+ assertArrayEquals(new int[] {}, ArrayUtils.concat((int[]) null, null));
+ assertArrayEquals(new int[] { 10000000, 20000000, 30000000 },
ArrayUtils.concat(new int[] { 10000000, 20000000, 30000000 }, null));
+ assertArrayEquals(new int[] { -30000000, 20000000, 10000000 },
ArrayUtils.concat(null, new int[] { -30000000, 20000000, 10000000 }));
+ assertArrayEquals(new int[] { 10000000, 30000000, 20000000, 100000000,
70000000 },
+ ArrayUtils.concat(new int[] { 10000000, 30000000, 20000000 },
new int[] { 100000000, 70000000 }));
+ }
+
+ @Test
+ void testLongArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((long[][]) null));
+ assertArrayEquals(new long[] {}, ArrayUtils.concat((long[]) null,
null));
+ assertArrayEquals(new long[] { 10000000000L, 20000000000L,
30000000000L },
+ ArrayUtils.concat(new long[] { 10000000000L, 20000000000L,
30000000000L }, null));
+ assertArrayEquals(new long[] { -30000000000L, 20000000000L,
10000000000L },
+ ArrayUtils.concat(null, new long[] { -30000000000L,
20000000000L, 10000000000L }));
+ assertArrayEquals(new long[] { 10000000000L, 30000000000L,
20000000000L, 100000000000L, 70000000000L },
+ ArrayUtils.concat(new long[] { 10000000000L, 30000000000L,
20000000000L }, new long[] { 100000000000L, 70000000000L }));
+ }
+
+ @Test
+ void testShortArraysConcat() {
+ assertThrows(NullPointerException.class, () ->
ArrayUtils.concat((short[][]) null));
+ assertArrayEquals(new short[] {}, ArrayUtils.concat((short[]) null,
null));
+ assertArrayEquals(new short[] { 1000, 2000, 3000 },
ArrayUtils.concat(new short[] { 1000, 2000, 3000 }, null));
+ assertArrayEquals(new short[] { -3000, 2000, 1000 },
ArrayUtils.concat(null, new short[] { -3000, 2000, 1000 }));
+ assertArrayEquals(new short[] { 1000, 3000, 2000, 10000, 7000 },
ArrayUtils.concat(new short[] { 1000, 3000, 2000 }, new short[] { 10000, 7000
}));
+ }
+}