From 0beaad9e10ee1d887e40734f88d520cacd610ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Tue, 7 Apr 2026 12:19:37 +0800 Subject: [PATCH 1/8] Replace commons StringUtils usages with Paimon StringUtils --- .../org/apache/paimon/utils/StringUtils.java | 106 ++++++++++++++++++ .../metric/cpu/ProcfsBasedProcessTree.java | 2 +- .../apache/paimon/benchmark/Benchmark.java | 3 +- .../apache/paimon/utils/StringUtilsTest.java | 50 +++++++++ .../paimon/catalog/TableQueryAuthResult.java | 3 +- .../org/apache/paimon/rest/RESTCatalog.java | 3 +- .../apache/paimon/rest/RESTCatalogServer.java | 2 +- .../apache/paimon/rest/RESTCatalogTest.java | 2 +- .../procedure/CreateBranchProcedure.java | 3 +- .../flink/action/CreateBranchAction.java | 2 +- .../procedure/CreateBranchProcedure.java | 3 +- .../apache/paimon/flink/BranchSqlITCase.java | 3 +- .../paimon/flink/CatalogTableITCase.java | 3 +- .../apache/paimon/flink/ShowCreateUtil.java | 2 +- .../paimon/spark/SparkReadTestBase.java | 2 +- .../spark/SparkSchemaEvolutionITCase.java | 2 +- tools/maven/checkstyle.xml | 14 +-- 17 files changed, 178 insertions(+), 27 deletions(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index c189de92e5d6..960b67fccf46 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -307,6 +307,112 @@ public static boolean isNotEmpty(CharSequence cs) { return !isEmpty(cs); } + public static boolean isBlank(final CharSequence cs) { + if (isEmpty(cs)) { + return true; + } + + for (int i = 0; i < cs.length(); i++) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + return true; + } + + public static boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null || cs1.length() != cs2.length()) { + return false; + } + + for (int i = 0; i < cs1.length(); i++) { + if (cs1.charAt(i) != cs2.charAt(i)) { + return false; + } + } + return true; + } + + public static boolean startsWith(final CharSequence str, final CharSequence prefix) { + if (str == prefix) { + return true; + } + if (str == null || prefix == null) { + return false; + } + + return str.toString().startsWith(prefix.toString()); + } + + public static boolean endsWith(final CharSequence str, final CharSequence suffix) { + if (str == suffix) { + return true; + } + if (str == null || suffix == null) { + return false; + } + + return str.toString().endsWith(suffix.toString()); + } + + public static String substringBeforeLast(final String str, final String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str; + } + + int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str; + } + + return str.substring(0, pos); + } + + public static String substringAfterLast(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (isEmpty(separator)) { + return EMPTY; + } + + int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) { + return EMPTY; + } + + return str.substring(pos + separator.length()); + } + + public static String stripEnd(final String str, final String stripChars) { + if (isEmpty(str)) { + return str; + } + + int end = str.length(); + if (stripChars == null) { + while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + } else if (stripChars.isEmpty()) { + return str; + } else { + while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) { + end--; + } + } + + return str.substring(0, end); + } + + public static String trimToNull(final String str) { + String trimmed = trim(str); + return isEmpty(trimmed) ? null : trimmed; + } + public static String randomNumericString(int len) { StringBuilder builder = new StringBuilder(); ThreadLocalRandom rnd = ThreadLocalRandom.current(); diff --git a/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java b/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java index b1a9b3fd8699..3d3ff96df476 100644 --- a/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java +++ b/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java @@ -26,7 +26,7 @@ import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.RegexFileFilter; import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import java.io.BufferedReader; import java.io.File; diff --git a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/Benchmark.java b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/Benchmark.java index 939f8cf90bdc..450e7035a40b 100644 --- a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/Benchmark.java +++ b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/Benchmark.java @@ -18,7 +18,8 @@ package org.apache.paimon.benchmark; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; + import org.apache.paimon.shade.org.apache.commons.lang3.SystemUtils; import java.io.BufferedReader; diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java index b281812a13d7..b80d5bc2bf7d 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java @@ -259,6 +259,56 @@ void testNonEmptyStringBuilder() { } } + @Nested + class CommonsCompatibilityTests { + + @Test + void testIsBlank() { + assertThat(StringUtils.isBlank(null)).isTrue(); + assertThat(StringUtils.isBlank("")).isTrue(); + assertThat(StringUtils.isBlank(" \t")).isTrue(); + assertThat(StringUtils.isBlank("paimon")).isFalse(); + } + + @Test + void testEquals() { + assertThat(StringUtils.equals(null, null)).isTrue(); + assertThat(StringUtils.equals(null, "paimon")).isFalse(); + assertThat(StringUtils.equals("paimon", "paimon")).isTrue(); + assertThat(StringUtils.equals("paimon", "Paimon")).isFalse(); + } + + @Test + void testStartsWithAndEndsWith() { + assertThat(StringUtils.startsWith("manifest-1", "manifest")).isTrue(); + assertThat(StringUtils.startsWith(null, "manifest")).isFalse(); + assertThat(StringUtils.endsWith("part-0.parquet", ".parquet")).isTrue(); + assertThat(StringUtils.endsWith("part-0.orc", ".parquet")).isFalse(); + } + + @Test + void testSubstringBeforeAndAfterLast() { + assertThat(StringUtils.substringBeforeLast("a/b/c", "/")).isEqualTo("a/b"); + assertThat(StringUtils.substringBeforeLast("abc", "/")).isEqualTo("abc"); + assertThat(StringUtils.substringAfterLast("a/b/c", "/")).isEqualTo("c"); + assertThat(StringUtils.substringAfterLast("abc/", "/")).isEmpty(); + } + + @Test + void testStripEnd() { + assertThat(StringUtils.stripEnd("cpu\n", null)).isEqualTo("cpu"); + assertThat(StringUtils.stripEnd("abccc", "c")).isEqualTo("ab"); + assertThat(StringUtils.stripEnd("abc", "")).isEqualTo("abc"); + } + + @Test + void testTrimToNull() { + assertThat(StringUtils.trimToNull(null)).isNull(); + assertThat(StringUtils.trimToNull(" ")).isNull(); + assertThat(StringUtils.trimToNull(" paimon ")).isEqualTo("paimon"); + } + } + @Nested class RandomNumericStringTests { diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/TableQueryAuthResult.java b/paimon-core/src/main/java/org/apache/paimon/catalog/TableQueryAuthResult.java index 87910b5cc65a..b0bb9b1d520d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/catalog/TableQueryAuthResult.java +++ b/paimon-core/src/main/java/org/apache/paimon/catalog/TableQueryAuthResult.java @@ -36,8 +36,7 @@ import org.apache.paimon.types.RowType; import org.apache.paimon.utils.InternalRowUtils; import org.apache.paimon.utils.JsonSerdeUtil; - -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import javax.annotation.Nullable; diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java index 364cdb3cd591..e81e6c8e6108 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java @@ -63,13 +63,12 @@ import org.apache.paimon.table.system.SystemTableLoader; import org.apache.paimon.utils.Pair; import org.apache.paimon.utils.SnapshotNotExistException; +import org.apache.paimon.utils.StringUtils; import org.apache.paimon.view.View; import org.apache.paimon.view.ViewChange; import org.apache.paimon.view.ViewImpl; import org.apache.paimon.view.ViewSchema; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; - import javax.annotation.Nullable; import java.io.IOException; diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java index 5961101c3a55..41caed02637b 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java @@ -111,6 +111,7 @@ import org.apache.paimon.utils.LazyField; import org.apache.paimon.utils.Pair; import org.apache.paimon.utils.SnapshotManager; +import org.apache.paimon.utils.StringUtils; import org.apache.paimon.utils.TagManager; import org.apache.paimon.utils.TimeUtils; import org.apache.paimon.view.View; @@ -120,7 +121,6 @@ import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java index e70a11321aff..78d8cd8e00a4 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java @@ -90,6 +90,7 @@ import org.apache.paimon.types.DataTypes; import org.apache.paimon.utils.SnapshotManager; import org.apache.paimon.utils.SnapshotNotExistException; +import org.apache.paimon.utils.StringUtils; import org.apache.paimon.view.View; import org.apache.paimon.view.ViewChange; @@ -97,7 +98,6 @@ import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap; import org.apache.paimon.shade.guava30.com.google.common.collect.Lists; import org.apache.paimon.shade.guava30.com.google.common.collect.Maps; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Assertions; diff --git a/paimon-flink/paimon-flink-1.18/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java b/paimon-flink/paimon-flink-1.18/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java index a7a8fb995271..547cbfecd33d 100644 --- a/paimon-flink/paimon-flink-1.18/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java +++ b/paimon-flink/paimon-flink-1.18/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java @@ -21,8 +21,7 @@ import org.apache.paimon.catalog.Catalog; import org.apache.paimon.catalog.Identifier; import org.apache.paimon.table.Table; - -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.flink.table.procedure.ProcedureContext; diff --git a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CreateBranchAction.java b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CreateBranchAction.java index 0d681cae73bb..9e65878c99e7 100644 --- a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CreateBranchAction.java +++ b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CreateBranchAction.java @@ -18,7 +18,7 @@ package org.apache.paimon.flink.action; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import java.util.Map; diff --git a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java index 90cb5f8cd9ac..d6287cff56a3 100644 --- a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java +++ b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateBranchProcedure.java @@ -21,8 +21,7 @@ import org.apache.paimon.catalog.Catalog; import org.apache.paimon.catalog.Identifier; import org.apache.paimon.table.Table; - -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.flink.table.annotation.ArgumentHint; import org.apache.flink.table.annotation.DataTypeHint; diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BranchSqlITCase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BranchSqlITCase.java index 641efb733595..0fd5b81de43c 100644 --- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BranchSqlITCase.java +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BranchSqlITCase.java @@ -22,8 +22,7 @@ import org.apache.paimon.table.FileStoreTable; import org.apache.paimon.utils.BlockingIterator; import org.apache.paimon.utils.SnapshotManager; - -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.flink.types.Row; import org.apache.flink.types.RowKind; diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java index 0bea96f04630..7a82007870a1 100644 --- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java @@ -20,8 +20,7 @@ import org.apache.paimon.catalog.Catalog; import org.apache.paimon.utils.BlockingIterator; - -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.flink.table.catalog.CatalogPartition; import org.apache.flink.table.catalog.CatalogPartitionSpec; diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/ShowCreateUtil.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/ShowCreateUtil.java index 415135ae514d..50ed3bf532dd 100644 --- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/ShowCreateUtil.java +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/ShowCreateUtil.java @@ -18,7 +18,7 @@ package org.apache.paimon.flink; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.flink.table.api.TableException; import org.apache.flink.table.catalog.CatalogBaseTable; diff --git a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java index 27a7557ab75a..771d65a840da 100644 --- a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java +++ b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java @@ -35,7 +35,7 @@ import org.apache.paimon.types.DataField; import org.apache.paimon.types.RowKind; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; diff --git a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java index ea837960cc40..1cdf210d89d4 100644 --- a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java +++ b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java @@ -18,7 +18,7 @@ package org.apache.paimon.spark; -import org.apache.paimon.shade.org.apache.commons.lang3.StringUtils; +import org.apache.paimon.utils.StringUtils; import org.apache.spark.sql.AnalysisException; import org.apache.spark.sql.Dataset; diff --git a/tools/maven/checkstyle.xml b/tools/maven/checkstyle.xml index 998c56383f66..2e8334375f9f 100644 --- a/tools/maven/checkstyle.xml +++ b/tools/maven/checkstyle.xml @@ -189,12 +189,6 @@ This file is based on the checkstyle file of Apache Beam. - - - - - - @@ -276,6 +270,13 @@ This file is based on the checkstyle file of Apache Beam. + + + + + @@ -588,4 +589,3 @@ This file is based on the checkstyle file of Apache Beam. - From c1100d8c6dd37bc4a065958e058971bf086f6991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 15:32:19 +0800 Subject: [PATCH 2/8] Fix minus --- .../paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java | 2 +- .../test/java/org/apache/paimon/spark/SparkReadTestBase.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java b/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java index 3d3ff96df476..e500d2820c53 100644 --- a/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java +++ b/paimon-benchmark/paimon-cluster-benchmark/src/main/java/org/apache/paimon/benchmark/metric/cpu/ProcfsBasedProcessTree.java @@ -20,13 +20,13 @@ import org.apache.paimon.benchmark.metric.cpu.clock.Clock; import org.apache.paimon.benchmark.metric.cpu.clock.SystemClock; +import org.apache.paimon.utils.StringUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.AndFileFilter; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.RegexFileFilter; import org.apache.commons.lang3.ArrayUtils; -import org.apache.paimon.utils.StringUtils; import java.io.BufferedReader; import java.io.File; diff --git a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java index 771d65a840da..918552fb06fa 100644 --- a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java +++ b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadTestBase.java @@ -34,7 +34,6 @@ import org.apache.paimon.table.sink.TableCommitImpl; import org.apache.paimon.types.DataField; import org.apache.paimon.types.RowKind; - import org.apache.paimon.utils.StringUtils; import org.apache.spark.sql.Dataset; From dffcb88cf7a42155e92e1bad53631019e8a0894e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 15:49:24 +0800 Subject: [PATCH 3/8] Fix test --- .../org/apache/paimon/utils/StringUtils.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index 960b67fccf46..d8a55f20948a 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -551,6 +551,58 @@ public static String[] split( return list.toArray(new String[list.size()]); } + /** + * Joins the elements of the provided array into a single String containing the provided list of + * elements. + * + *

No delimiter is added before or after the list. A {@code null} separator is the same as an + * empty String (""). + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null array input + */ + public static String join(final Object[] array, final String separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + * Joins the elements of the provided array into a single String containing the provided list of + * elements. + * + *

No delimiter is added before or after the list. A {@code null} separator is the same as an + * empty String (""). + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @param startIndex the first index to start joining from + * @param endIndex the index to stop joining from (exclusive) + * @return the joined String, {@code null} if null array input + */ + public static String join( + final Object[] array, final String separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + /** * Joins the elements of the provided {@code Iterable} into a single String containing the * provided elements. From 3f74cdcf366601b9750fe32e378966811bdc7a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 16:08:49 +0800 Subject: [PATCH 4/8] Fix test --- .../src/main/java/org/apache/paimon/utils/StringUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index d8a55f20948a..2b8f3f395c4b 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -583,7 +583,10 @@ public static String join(final Object[] array, final String separator) { * @return the joined String, {@code null} if null array input */ public static String join( - final Object[] array, final String separator, final int startIndex, final int endIndex) { + final Object[] array, + final String separator, + final int startIndex, + final int endIndex) { if (array == null) { return null; } From d5787f77fea331b67fcb72282ad7e04d83a9d625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 18:02:00 +0800 Subject: [PATCH 5/8] Fix minus --- .../src/main/java/org/apache/paimon/utils/StringUtils.java | 3 ++- .../test/java/org/apache/paimon/utils/StringUtilsTest.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index 2b8f3f395c4b..c935a9309d54 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -594,10 +594,11 @@ public static String join( if (noOfItems <= 0) { return EMPTY; } + final String actualSeparator = separator == null ? EMPTY : separator; final StringBuilder buf = new StringBuilder(noOfItems * 16); for (int i = startIndex; i < endIndex; i++) { if (i > startIndex) { - buf.append(separator); + buf.append(actualSeparator); } if (array[i] != null) { buf.append(array[i]); diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java index b80d5bc2bf7d..96b95003a79d 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java @@ -380,6 +380,11 @@ void testJoinIterableBasicCases() { assertThat(StringUtils.join(Arrays.asList("a", null, "c"), ",")).isEqualTo("a,,c"); } + @Test + void testJoinArrayNullSeparator() { + assertThat(StringUtils.join(new Object[] {"a", "b", "c"}, null)).isEqualTo("abc"); + } + @Test void testJoinIterableEdgeCases() { assertThat(StringUtils.join((Iterable) null, ",")).isNull(); From a7ecc89cead93457c47732388bd63983b58158a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 18:03:16 +0800 Subject: [PATCH 6/8] Fix minus --- .../org/apache/paimon/utils/StringUtils.java | 6 ++++++ .../apache/paimon/utils/StringUtilsTest.java | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index c935a9309d54..f830e706a9f5 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -590,6 +590,12 @@ public static String join( if (array == null) { return null; } + if (startIndex < 0 || startIndex >= array.length) { + throw new ArrayIndexOutOfBoundsException(startIndex); + } + if (endIndex < 0 || endIndex > array.length) { + throw new ArrayIndexOutOfBoundsException(endIndex); + } final int noOfItems = endIndex - startIndex; if (noOfItems <= 0) { return EMPTY; diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java index 96b95003a79d..b7840bcffe7c 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java @@ -385,6 +385,23 @@ void testJoinArrayNullSeparator() { assertThat(StringUtils.join(new Object[] {"a", "b", "c"}, null)).isEqualTo("abc"); } + @Test + void testJoinArrayStartAndEndIndex() { + assertThat(StringUtils.join(new Object[] {"a", "b", "c"}, "--", 1, 3)) + .isEqualTo("b--c"); + assertThat(StringUtils.join(new Object[] {"a", "b", "c"}, "--", 2, 2)).isEmpty(); + } + + @Test + void testJoinArrayInvalidIndex() { + assertThatThrownBy(() -> StringUtils.join(new Object[] {"a", "b", "c"}, ",", 0, -1)) + .isInstanceOf(ArrayIndexOutOfBoundsException.class); + assertThatThrownBy(() -> StringUtils.join(new Object[] {"a", "b", "c"}, ",", 3, 3)) + .isInstanceOf(ArrayIndexOutOfBoundsException.class); + assertThatThrownBy(() -> StringUtils.join(new Object[] {"a", "b", "c"}, ",", 0, 4)) + .isInstanceOf(ArrayIndexOutOfBoundsException.class); + } + @Test void testJoinIterableEdgeCases() { assertThat(StringUtils.join((Iterable) null, ",")).isNull(); From fe97cd329715f011ab2c1e8428d0059a9356de61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 18:14:37 +0800 Subject: [PATCH 7/8] Fix minus --- .../org/apache/paimon/utils/StringUtils.java | 29 ++++++------------- .../apache/paimon/utils/StringUtilsTest.java | 8 +++-- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index f830e706a9f5..88837ea4cbe4 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -308,11 +308,8 @@ public static boolean isNotEmpty(CharSequence cs) { } public static boolean isBlank(final CharSequence cs) { - if (isEmpty(cs)) { - return true; - } - - for (int i = 0; i < cs.length(); i++) { + final int strLen = cs == null ? 0 : cs.length(); + for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(cs.charAt(i))) { return false; } @@ -337,24 +334,16 @@ public static boolean equals(final CharSequence cs1, final CharSequence cs2) { } public static boolean startsWith(final CharSequence str, final CharSequence prefix) { - if (str == prefix) { - return true; - } if (str == null || prefix == null) { - return false; + return str == null && prefix == null; } - return str.toString().startsWith(prefix.toString()); } public static boolean endsWith(final CharSequence str, final CharSequence suffix) { - if (str == suffix) { - return true; - } if (str == null || suffix == null) { - return false; + return str == null && suffix == null; } - return str.toString().endsWith(suffix.toString()); } @@ -562,11 +551,11 @@ public static String[] split( * @param separator the separator character to use, null treated as "" * @return the joined String, {@code null} if null array input */ - public static String join(final Object[] array, final String separator) { + public static String join(final Object[] array, final String delimiter) { if (array == null) { return null; } - return join(array, separator, 0, array.length); + return join(array, delimiter, 0, array.length); } /** @@ -584,7 +573,7 @@ public static String join(final Object[] array, final String separator) { */ public static String join( final Object[] array, - final String separator, + final String delimiter, final int startIndex, final int endIndex) { if (array == null) { @@ -600,11 +589,11 @@ public static String join( if (noOfItems <= 0) { return EMPTY; } - final String actualSeparator = separator == null ? EMPTY : separator; + Objects.requireNonNull(delimiter, "The delimiter must not be null"); final StringBuilder buf = new StringBuilder(noOfItems * 16); for (int i = startIndex; i < endIndex; i++) { if (i > startIndex) { - buf.append(actualSeparator); + buf.append(delimiter); } if (array[i] != null) { buf.append(array[i]); diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java index b7840bcffe7c..c4cb9b95bc4f 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/StringUtilsTest.java @@ -282,8 +282,10 @@ void testEquals() { void testStartsWithAndEndsWith() { assertThat(StringUtils.startsWith("manifest-1", "manifest")).isTrue(); assertThat(StringUtils.startsWith(null, "manifest")).isFalse(); + assertThat(StringUtils.startsWith(null, null)).isTrue(); assertThat(StringUtils.endsWith("part-0.parquet", ".parquet")).isTrue(); assertThat(StringUtils.endsWith("part-0.orc", ".parquet")).isFalse(); + assertThat(StringUtils.endsWith(null, null)).isTrue(); } @Test @@ -381,8 +383,10 @@ void testJoinIterableBasicCases() { } @Test - void testJoinArrayNullSeparator() { - assertThat(StringUtils.join(new Object[] {"a", "b", "c"}, null)).isEqualTo("abc"); + void testJoinArrayNullDelimiter() { + assertThatThrownBy(() -> StringUtils.join(new Object[] {"a", "b", "c"}, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("The delimiter must not be null"); } @Test From a04785e0f9ef922f77dc328a3ab5d767b6670714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=9F=E5=BC=8B?= Date: Fri, 10 Apr 2026 18:17:08 +0800 Subject: [PATCH 8/8] Fix minus --- .../src/main/java/org/apache/paimon/utils/StringUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java index 88837ea4cbe4..4a46deca4d37 100644 --- a/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java +++ b/paimon-api/src/main/java/org/apache/paimon/utils/StringUtils.java @@ -548,7 +548,7 @@ public static String[] split( * empty String (""). * * @param array the array of values to join together, may be null - * @param separator the separator character to use, null treated as "" + * @param delimiter the separator character to use, null treated as "" * @return the joined String, {@code null} if null array input */ public static String join(final Object[] array, final String delimiter) { @@ -566,7 +566,7 @@ public static String join(final Object[] array, final String delimiter) { * empty String (""). * * @param array the array of values to join together, may be null - * @param separator the separator character to use, null treated as "" + * @param delimiter the separator character to use, null treated as "" * @param startIndex the first index to start joining from * @param endIndex the index to stop joining from (exclusive) * @return the joined String, {@code null} if null array input