+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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. + *+ * 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. + *
+ * + * @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 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})}. *
@@ -9344,4 +9613,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 00000000000..26f869e1e64
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsConcatTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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 for ArrayUtils concat methods
+ */
+class ArrayUtilsConcatTest extends AbstractLangTest {
+
+ @Test
+ void testBooleanArraysConcat() {
+ 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 testCharArraysConcat() {
+ 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 testByteArraysConcat() {
+ 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 testShortArraysConcat() {
+ 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})
+ );
+ }
+
+ @Test
+ void testIntArraysConcat() {
+ 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() {
+ 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 testFloatArraysConcat() {
+ 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 testDoubleArraysConcat() {
+ 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