diff --git a/independent-projects/bootstrap/benchmarks/pom.xml b/independent-projects/bootstrap/benchmarks/pom.xml
new file mode 100644
index 0000000000000..3e7b7d754c161
--- /dev/null
+++ b/independent-projects/bootstrap/benchmarks/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+
+ quarkus-bootstrap-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ benchmarks
+ Quarkus - Bootstrap - JMH Benchmarks
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-bom
+ ${project.version}
+ pom
+ import
+
+
+ io.quarkus
+ quarkus-bootstrap-bom-test
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+ provided
+
+
+ io.quarkus
+ quarkus-classloader-commons
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ benchmark
+
+
+ org.openjdk.jmh.Main
+
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/independent-projects/bootstrap/benchmarks/src/main/java/io/quarkus/commons/benchmarks/BenchmarkClassnameSanitize.java b/independent-projects/bootstrap/benchmarks/src/main/java/io/quarkus/commons/benchmarks/BenchmarkClassnameSanitize.java
new file mode 100644
index 0000000000000..f6ea0a1be7054
--- /dev/null
+++ b/independent-projects/bootstrap/benchmarks/src/main/java/io/quarkus/commons/benchmarks/BenchmarkClassnameSanitize.java
@@ -0,0 +1,65 @@
+package io.quarkus.commons.benchmarks;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+import io.quarkus.commons.classloading.ClassloadHelper;
+
+@State(Scope.Benchmark)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@Warmup(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(2)
+public class BenchmarkClassnameSanitize {
+
+ @Param({ "true", "false" })
+ public boolean worseCase;
+
+ private static final String TEST_CLASS_NAME_BAD = "/some/odd/package/and/ClassName/";
+ private static final String TEST_CLASS_NAME_GOOD = "some/odd/package/and/ClassName";
+
+ private String TESTINPUT;
+
+ @Setup
+ public void setup() {
+ TESTINPUT = worseCase ? TEST_CLASS_NAME_BAD : TEST_CLASS_NAME_GOOD;
+ }
+
+ @Benchmark
+ public String sanitizeNameOptimised() {
+ return ClassloadHelper.sanitizeName(TESTINPUT);
+ }
+
+ @Benchmark
+ public String sanitizeNameStandard() {
+ return legacySanitize(TESTINPUT);
+ }
+
+ private String legacySanitize(String name) {
+ if (name.startsWith("/")) {
+ name = name.substring(1);
+ }
+ if (name.endsWith("/")) {
+ name = name.substring(0, name.length() - 1);
+ }
+ return name;
+ }
+
+ public static void main(String[] args) throws IOException {
+ org.openjdk.jmh.Main.main(args);
+ }
+
+}
diff --git a/independent-projects/bootstrap/bom/pom.xml b/independent-projects/bootstrap/bom/pom.xml
index 5db6112b194ad..2143268dbc458 100644
--- a/independent-projects/bootstrap/bom/pom.xml
+++ b/independent-projects/bootstrap/bom/pom.xml
@@ -47,6 +47,11 @@
commons-lang3
${commons-lang.version}
+
+ io.quarkus
+ quarkus-classloader-commons
+ ${project.version}
+
io.quarkus
quarkus-bootstrap-core
diff --git a/independent-projects/bootstrap/classloader-commons/pom.xml b/independent-projects/bootstrap/classloader-commons/pom.xml
new file mode 100644
index 0000000000000..15f3c98893323
--- /dev/null
+++ b/independent-projects/bootstrap/classloader-commons/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ quarkus-bootstrap-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-classloader-commons
+ Quarkus - Bootstrap - Classloader common utilities
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-bom
+ ${project.version}
+ pom
+ import
+
+
+ io.quarkus
+ quarkus-bootstrap-bom-test
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+
\ No newline at end of file
diff --git a/independent-projects/bootstrap/classloader-commons/src/main/java/io/quarkus/commons/classloading/ClassloadHelper.java b/independent-projects/bootstrap/classloader-commons/src/main/java/io/quarkus/commons/classloading/ClassloadHelper.java
new file mode 100644
index 0000000000000..cfe0dd6049b5e
--- /dev/null
+++ b/independent-projects/bootstrap/classloader-commons/src/main/java/io/quarkus/commons/classloading/ClassloadHelper.java
@@ -0,0 +1,34 @@
+package io.quarkus.commons.classloading;
+
+public final class ClassloadHelper {
+
+ private ClassloadHelper() {
+ //Not meant to be instantiated
+ }
+
+ /**
+ * Sanitizes the string passed as argument, by removing any "/"
+ * symbol at the end or at the beginning.
+ *
+ * @param name the string to be sanitized
+ * @return a new String with the offending characters removed, or potentially the same String
+ * if no modifications were required.
+ */
+ public static String sanitizeName(final String name) {
+ final int length = name.length();
+ if (length == 0) {
+ return name;
+ }
+ final int beginIndex = (name.charAt(0) == '/') ? 1 : 0;
+ final int endIndex = (name.charAt(length - 1) == '/') ? length - 1 : length;
+ if (beginIndex == 0 && endIndex == length) {
+ return name;
+ } else {
+ if (beginIndex > endIndex) {
+ return "";
+ }
+ return name.substring(beginIndex, endIndex);
+ }
+ }
+
+}
diff --git a/independent-projects/bootstrap/classloader-commons/src/test/java/io/quarkus/commons/classloading/ClassloadHelperTest.java b/independent-projects/bootstrap/classloader-commons/src/test/java/io/quarkus/commons/classloading/ClassloadHelperTest.java
new file mode 100644
index 0000000000000..a6bb841d8cb4f
--- /dev/null
+++ b/independent-projects/bootstrap/classloader-commons/src/test/java/io/quarkus/commons/classloading/ClassloadHelperTest.java
@@ -0,0 +1,27 @@
+package io.quarkus.commons.classloading;
+
+import static io.quarkus.commons.classloading.ClassloadHelper.sanitizeName;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ClassloadHelperTest {
+
+ @Test
+ public void testSanitizeNameSpec() {
+ Assertions.assertEquals("", sanitizeName(""));
+ Assertions.assertEquals("", sanitizeName("/"));
+ Assertions.assertEquals("", sanitizeName("//"));
+ Assertions.assertEquals("a", sanitizeName("/a/"));
+ Assertions.assertEquals("a", sanitizeName("/a"));
+ Assertions.assertEquals("a", sanitizeName("a/"));
+ Assertions.assertEquals("abcd", sanitizeName("/abcd/"));
+ Assertions.assertEquals("ab/cd", sanitizeName("/ab/cd/"));
+ Assertions.assertEquals("ab/cd/something/something", sanitizeName("/ab/cd/something/something"));
+ //The following cases are left out intentionally as they would IMO
+ //fall in the cathegory of illegal input, and not worth sanitising for:
+ // "/abcd//" : multiple slashes at end[begin]
+ // "/abcd//abds/" :multiple slashes in the middle
+ }
+
+}
diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml
index fe05e4cb1f81b..1fd1e4571a880 100644
--- a/independent-projects/bootstrap/core/pom.xml
+++ b/independent-projects/bootstrap/core/pom.xml
@@ -33,6 +33,10 @@
+
+ io.quarkus
+ quarkus-classloader-commons
+
io.quarkus
quarkus-bootstrap-app-model
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
index e1b20fe85d657..7ce1cdece5cba 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
@@ -1,5 +1,7 @@
package io.quarkus.bootstrap.classloading;
+import static io.quarkus.commons.classloading.ClassloadHelper.sanitizeName;
+
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -153,16 +155,6 @@ public static Builder builder(String name, ClassLoader parent, boolean parentFir
return new Builder(name, parent, parentFirst);
}
- private String sanitizeName(String name) {
- if (name.startsWith("/")) {
- name = name.substring(1);
- }
- if (name.endsWith("/")) {
- name = name.substring(0, name.length() - 1);
- }
- return name;
- }
-
/**
* Returns true if the supplied class is a class that would be loaded parent-first
*/
@@ -295,7 +287,7 @@ private ClassLoaderState getState() {
Map> elementMap = new HashMap<>();
for (ClassPathElement element : elements) {
for (String i : element.getProvidedResources()) {
- if (i.startsWith("/")) {
+ if (i.charAt(0) == '/') {
throw new RuntimeException(
"Resources cannot start with /, " + i + " is incorrect provided by " + element);
}
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
index c2f192de80593..db44c42aa19be 100644
--- a/independent-projects/bootstrap/pom.xml
+++ b/independent-projects/bootstrap/pom.xml
@@ -38,6 +38,7 @@
3.2.1
3.2.5
3.1.7
+ 1.37
3.25.3
@@ -89,6 +90,8 @@
core
runner
gradle-resolver
+ classloader-commons
+ benchmarks
diff --git a/independent-projects/bootstrap/runner/pom.xml b/independent-projects/bootstrap/runner/pom.xml
index f32872fa86a74..8549283dd2c6d 100644
--- a/independent-projects/bootstrap/runner/pom.xml
+++ b/independent-projects/bootstrap/runner/pom.xml
@@ -38,6 +38,10 @@
+
+ io.quarkus
+ quarkus-classloader-commons
+
io.smallrye.common
smallrye-common-io
diff --git a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/RunnerClassLoader.java b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/RunnerClassLoader.java
index 62c02e8fe08d3..872cdf9e4bb4b 100644
--- a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/RunnerClassLoader.java
+++ b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/RunnerClassLoader.java
@@ -1,5 +1,7 @@
package io.quarkus.bootstrap.runner;
+import static io.quarkus.commons.classloading.ClassloadHelper.sanitizeName;
+
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
@@ -208,13 +210,6 @@ protected URL findResource(String name) {
return null;
}
- private String sanitizeName(final String name) {
- if (name.length() > 0 && name.charAt(0) == '/') {
- return name.substring(1);
- }
- return name;
- }
-
private ClassLoadingResource[] getClassLoadingResources(final String name) {
ClassLoadingResource[] resources = directlyIndexedResourcesIndexMap.get(name);
if (resources != null) {