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 
}));
+    }
+}

Reply via email to