diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java index 852e3c9147f89..2a9a7f0462fb5 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java @@ -356,9 +356,7 @@ public QuarkusClassLoader createDeploymentClassLoader() { if (configuredClassLoading.isRemovedArtifact(dependency.getKey())) { continue; } - if (dependency.isRuntimeCp() && dependency.isJar() && - (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) || - configuredClassLoading.isReloadableArtifact(dependency.getKey()))) { + if (isReloadableRuntimeDependency(dependency)) { processCpElement(dependency, element -> addCpElement(builder, dependency, element)); } } @@ -368,6 +366,12 @@ public QuarkusClassLoader createDeploymentClassLoader() { return builder.build(); } + private boolean isReloadableRuntimeDependency(ResolvedDependency dependency) { + return dependency.isRuntimeCp() && dependency.isJar() && + (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) || + configuredClassLoading.isReloadableArtifact(dependency.getKey())); + } + public String getClassLoaderNameSuffix() { return quarkusBootstrap.getBaseName() != null ? " for " + quarkusBootstrap.getBaseName() : ""; } @@ -405,9 +409,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map addCpElement(builder, dependency, element)); } } 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 9368790bf7509..7f8ff90de6705 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 @@ -625,22 +625,77 @@ public List getElementsWithResource(String name) { public List getElementsWithResource(String name, boolean localOnly) { ensureOpen(name); - boolean parentFirst = parentFirst(name, getClassPathResourceIndex()); + final boolean parentFirst = parentFirst(name, getClassPathResourceIndex()); - List ret = new ArrayList<>(); + List result = List.of(); - if (parentFirst && !localOnly && parent instanceof QuarkusClassLoader) { - ret.addAll(((QuarkusClassLoader) parent).getElementsWithResource(name)); + if (parentFirst && !localOnly && parent instanceof QuarkusClassLoader parentQcl) { + result = parentQcl.getElementsWithResource(name); } - List classPathElements = getClassPathResourceIndex().getClassPathElements(name); - ret.addAll(classPathElements); + result = joinAndDedupe(result, getClassPathResourceIndex().getClassPathElements(name)); - if (!parentFirst && !localOnly && parent instanceof QuarkusClassLoader) { - ret.addAll(((QuarkusClassLoader) parent).getElementsWithResource(name)); + if (!parentFirst && !localOnly && parent instanceof QuarkusClassLoader parentQcl) { + result = joinAndDedupe(result, parentQcl.getElementsWithResource(name)); } - return ret; + return result; + } + + /** + * Returns a list containing elements from two lists eliminating duplicates. Elements from the first list + * will appear in the result before elements from the second list. + *

+ * The current implementation assumes that none of the lists contains duplicates on their own but some elements + * may be present in both lists. + * + * @param list1 first list + * @param list2 second list + * @return resulting list + */ + private static List joinAndDedupe(List list1, List list2) { + // it appears, in the vast majority of cases at least one of the lists will be empty + if (list1.isEmpty()) { + return list2; + } + if (list2.isEmpty()) { + return list1; + } + final List result = new ArrayList<>(list1.size() + list2.size()); + // it looks like in most cases at this point list1 (representing elements from the parent cl) will contain only one element + if (list1.size() == 1) { + final T firstCpe = list1.get(0); + result.add(firstCpe); + for (var cpe : list2) { + if (cpe != firstCpe) { + result.add(cpe); + } + } + return result; + } + result.addAll(list1); + for (var cpe : list2) { + if (!containsReference(list1, cpe)) { + result.add(cpe); + } + } + return result; + } + + /** + * Checks whether a list contains an element that references the other argument. + * + * @param list list of elements + * @param e element to look for + * @return true if the list contains an element referencing {@code e}, otherwise - false + */ + private static boolean containsReference(List list, T e) { + for (int i = list.size() - 1; i >= 0; --i) { + if (e == list.get(i)) { + return true; + } + } + return false; } public Set getReloadableClassNames() { @@ -902,6 +957,10 @@ public QuarkusClassLoader build() { return new QuarkusClassLoader(this); } + @Override + public String toString() { + return "QuarkusClassLoader.Builder:" + name + "@" + Integer.toHexString(hashCode()); + } } public ClassLoader parent() {