You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/10/23 08:27:46 UTC
(maven) branch master updated: feat: internalize plexus-classworlds (fixes #11028)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new 6399a8e556 feat: internalize plexus-classworlds (fixes #11028)
6399a8e556 is described below
commit 6399a8e556e52afdfb8b85e6b83179fdf41ad0f0
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Fri Oct 23 10:27:39 2026 +0200
feat: internalize plexus-classworlds (fixes #11028)
- Add impl/maven-classworlds: fork of plexus-classworlds with org.codehaus.plexus.classworlds implementation, backward-compat adapters under org.codehaus.classworlds, and new JPMS capabilities (module directive in m2.conf, META-INF/maven/module-access descriptor)
- Replace external plexus-classworlds dependency with maven-classworlds across all modules
- Remove classWorldsVersion property from root pom.xml
- Preserve org.codehaus.classworlds compat package until eclipse-sisu/sisu-project#280 is merged and released (tracked in #13693)
- Public API surface for classworlds deferred per review feedback
- Prerequisite for #13491 (JLine on module-path) and #13494 (per-plugin ModuleLayer support)
- Supersedes #11029
---
apache-maven/src/assembly/component.xml | 8 +-
compat/maven-compat/pom.xml | 4 +-
compat/maven-embedder/pom.xml | 4 +-
compat/maven-plugin-api/pom.xml | 4 +-
compat/maven-resolver-provider/pom.xml | 5 +
impl/maven-classworlds/pom.xml | 123 +++++
.../codehaus/classworlds/BytesURLConnection.java | 68 +++
.../classworlds/BytesURLStreamHandler.java | 65 +++
.../java/org/codehaus/classworlds/ClassRealm.java | 128 +++++
.../codehaus/classworlds/ClassRealmAdapter.java | 163 ++++++
.../classworlds/ClassRealmReverseAdapter.java | 141 ++++++
.../java/org/codehaus/classworlds/ClassWorld.java | 85 ++++
.../codehaus/classworlds/ClassWorldAdapter.java | 104 ++++
.../codehaus/classworlds/ClassWorldException.java | 122 +++++
.../classworlds/ClassWorldReverseAdapter.java | 113 +++++
.../classworlds/ConfigurationException.java | 96 ++++
.../org/codehaus/classworlds/Configurator.java | 151 ++++++
.../codehaus/classworlds/ConfiguratorAdapter.java | 92 ++++
.../codehaus/classworlds/DefaultClassRealm.java | 159 ++++++
.../classworlds/DuplicateRealmException.java | 114 +++++
.../java/org/codehaus/classworlds/Launcher.java | 76 +++
.../codehaus/classworlds/NoSuchRealmException.java | 114 +++++
.../org/codehaus/classworlds/package-info.java | 37 ++
.../codehaus/plexus/classworlds/ClassWorld.java | 305 ++++++++++++
.../plexus/classworlds/ClassWorldException.java | 88 ++++
.../plexus/classworlds/ClassWorldListener.java | 28 +-
.../org/codehaus/plexus/classworlds/UrlUtils.java | 70 +++
.../launcher/ConfigurationException.java | 66 +++
.../classworlds/launcher/ConfigurationHandler.java | 113 +++++
.../classworlds/launcher/ConfigurationParser.java | 492 ++++++++++++++++++
.../plexus/classworlds/launcher/Configurator.java | 357 +++++++++++++
.../plexus/classworlds/launcher/Interpolator.java | 168 +++++++
.../plexus/classworlds/launcher/Launcher.java | 365 ++++++++++++++
.../plexus/classworlds/realm/ClassRealm.java | 450 +++++++++++++++++
.../classworlds/realm/DuplicateRealmException.java | 83 +++
.../codehaus/plexus/classworlds/realm/Entry.java | 199 ++++++++
.../classworlds/realm/FilteredClassRealm.java | 74 +++
.../classworlds/realm/NoSuchRealmException.java | 83 +++
.../classworlds/strategy/AbstractStrategy.java | 80 +++
.../classworlds/strategy/OsgiBundleStrategy.java | 88 ++++
.../classworlds/strategy/ParentFirstStrategy.java | 81 +++
.../classworlds/strategy/SelfFirstStrategy.java | 81 +++
.../plexus/classworlds/strategy/Strategy.java | 56 +++
.../classworlds/strategy/StrategyFactory.java | 67 +++
.../classworlds/AbstractClassWorldsTestCase.java | 61 +++
.../org/codehaus/plexus/classworlds/ClassView.java | 130 +++++
.../plexus/classworlds/ClassWorldTest.java | 158 ++++++
.../plexus/classworlds/TestJarGenerator.java | 554 +++++++++++++++++++++
.../org/codehaus/plexus/classworlds/TestUtil.java | 79 +++
.../codehaus/plexus/classworlds/UrlUtilsTest.java | 49 ++
.../launcher/ConfigurationParserTest.java | 151 ++++++
.../classworlds/launcher/ConfiguratorTest.java | 360 +++++++++++++
.../plexus/classworlds/launcher/LauncherTest.java | 138 +++++
.../classworlds/realm/ClassRealmImplTest.java | 373 ++++++++++++++
.../classworlds/realm/DefaultClassRealmTest.java | 346 +++++++++++++
.../plexus/classworlds/realm/EntryTest.java | 186 +++++++
.../classworlds/realm/FilteredClassRealmTest.java | 107 ++++
.../classworlds/strategy/StrategyFactoryTest.java | 79 +++
.../plexus/classworlds/strategy/StrategyTest.java | 153 ++++++
.../src/test/test-data/a.properties | 18 +
.../src/test/test-data/dupe-main.conf | 3 +
.../src/test/test-data/dupe-realm.conf | 9 +
.../src/test/test-data/early-import.conf | 2 +
.../src/test/test-data/inheritance.conf | 18 +
.../src/test/test-data/launch-noclass.conf | 6 +
.../src/test/test-data/launch-nomethod.conf | 7 +
.../src/test/test-data/nested.properties | 18 +
.../src/test/test-data/optionally-existent.conf | 14 +
.../src/test/test-data/optionally-nonexistent.conf | 13 +
.../src/test/test-data/realm-syntax.conf | 3 +
.../src/test/test-data/resources/classworlds.conf | 12 +
.../src/test/test-data/set-using-existent.conf | 19 +
.../test/test-data/set-using-existent.properties | 19 +
.../src/test/test-data/set-using-missing.conf | 19 +
.../src/test/test-data/set-using-nonexistent.conf | 19 +
.../src/test/test-data/unhandled.conf | 2 +
.../test/test-data/valid-enh-launch-exitCode.conf | 6 +
.../src/test/test-data/valid-enh-launch.conf | 6 +
.../src/test/test-data/valid-from-from-from.conf | 24 +
.../src/test/test-data/valid-launch-exitCode.conf | 5 +
.../src/test/test-data/valid-launch.conf | 5 +
.../src/test/test-data/valid.conf | 24 +
impl/maven-cli/pom.xml | 4 +-
.../java/org/apache/maven/cling/MavenCling.java | 10 +-
impl/maven-core/pom.xml | 38 +-
.../internal/LifecycleDependencyResolver.java | 10 +-
.../plugin/internal/DefaultMavenPluginManager.java | 6 +-
.../project/DefaultProjectBuildingHelper.java | 9 +-
.../main/resources/META-INF/maven/extension.xml | 7 +-
.../classrealm/DefaultClassRealmManagerTest.java | 4 +-
impl/pom.xml | 1 +
.../extension/TestClassRealmManagerDelegate.java | 2 +-
.../TestClassRealmManagerDelegate.java | 2 +-
.../TestClassRealmManagerDelegate.java | 2 +-
pom.xml | 17 +-
src/graph/ReactorGraph.java | 2 +-
96 files changed, 8635 insertions(+), 44 deletions(-)
diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml
index 2298b21134..84f3bda039 100644
--- a/apache-maven/src/assembly/component.xml
+++ b/apache-maven/src/assembly/component.xml
@@ -23,14 +23,18 @@ under the License.
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>boot</outputDirectory>
<includes>
- <include>org.codehaus.plexus:plexus-classworlds</include>
+ <include>org.apache.maven:maven-classworlds</include>
</includes>
+ <!-- Preserve the legacy plexus-classworlds-*.jar file name for backward compatibility:
+ wrapper scripts, CI tooling, and IDEs that glob boot/plexus-classworlds-*.jar continue
+ to work without change after the internalization. -->
+ <outputFileNameMapping>plexus-classworlds-${artifact.version}.jar</outputFileNameMapping>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<excludes>
- <exclude>org.codehaus.plexus:plexus-classworlds</exclude>
+ <exclude>org.apache.maven:maven-classworlds</exclude>
</excludes>
</dependencySet>
</dependencySets>
diff --git a/compat/maven-compat/pom.xml b/compat/maven-compat/pom.xml
index 2f5439fcdd..6fd5e533f1 100644
--- a/compat/maven-compat/pom.xml
+++ b/compat/maven-compat/pom.xml
@@ -121,8 +121,8 @@ under the License.
</dependency>
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
diff --git a/compat/maven-embedder/pom.xml b/compat/maven-embedder/pom.xml
index 2df8588ec1..f1343b7bcf 100644
--- a/compat/maven-embedder/pom.xml
+++ b/compat/maven-embedder/pom.xml
@@ -146,8 +146,8 @@ under the License.
</dependency>
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
diff --git a/compat/maven-plugin-api/pom.xml b/compat/maven-plugin-api/pom.xml
index 9f8f42bbf8..94a7f005d2 100644
--- a/compat/maven-plugin-api/pom.xml
+++ b/compat/maven-plugin-api/pom.xml
@@ -79,8 +79,8 @@ under the License.
</exclusions>
</dependency>
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
diff --git a/compat/maven-resolver-provider/pom.xml b/compat/maven-resolver-provider/pom.xml
index 79c7507bec..7130c64d41 100644
--- a/compat/maven-resolver-provider/pom.xml
+++ b/compat/maven-resolver-provider/pom.xml
@@ -151,6 +151,11 @@ under the License.
<artifactId>plexus-testing</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
diff --git a/impl/maven-classworlds/pom.xml b/impl/maven-classworlds/pom.xml
new file mode 100644
index 0000000000..768d4199aa
--- /dev/null
+++ b/impl/maven-classworlds/pom.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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
+
+ http://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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-impl-modules</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>maven-classworlds</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Maven 4 Classworlds</name>
+ <description>A class loader framework</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>src/test/test-data/**</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <excludes>org/codehaus/classworlds/**</excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>org.codehaus.plexus.classworlds.launcher.Launcher</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ <argLine>-ea:org.codehaus.classworlds... -ea:org.codehaus.plexus.classworlds...</argLine>
+ <forkCount>1</forkCount>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>org/codehaus/plexus/classworlds/event/*</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <phase>generate-test-resources</phase>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant</artifactId>
+ <version>1.10.14</version>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <version>2.23.1</version>
+ </artifactItem>
+ <artifactItem>
+ <groupId>jakarta.xml.bind</groupId>
+ <artifactId>jakarta.xml.bind-api</artifactId>
+ <version>4.0.2</version>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${project.build.directory}/test-lib</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLConnection.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLConnection.java
new file mode 100644
index 0000000000..656e77cb09
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLConnection.java
@@ -0,0 +1,68 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * URLConnection implementation for byte arrays.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should avoid using this internal implementation detail.</p>
+ *
+ * @deprecated This is a legacy internal class.
+ */
+@Deprecated
+public class BytesURLConnection extends URLConnection {
+ protected final byte[] content;
+
+ protected int offset;
+
+ protected int length;
+
+ public BytesURLConnection(URL url, byte[] content) {
+ super(url);
+ this.content = content;
+ }
+
+ public void connect() {}
+
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(content);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLStreamHandler.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLStreamHandler.java
new file mode 100644
index 0000000000..182c572774
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLStreamHandler.java
@@ -0,0 +1,65 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * URLStreamHandler implementation for byte arrays.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should avoid using this internal implementation detail.</p>
+ *
+ * @author Hani Suleiman (hani@formicary.net) Date: Oct 20, 2003 12:45:18 AM
+ * @deprecated This is a legacy internal class.
+ */
+@Deprecated
+public class BytesURLStreamHandler extends URLStreamHandler {
+ final byte[] content;
+
+ int offset;
+
+ int length;
+
+ public BytesURLStreamHandler(byte[] content) {
+ this.content = content;
+ }
+
+ public URLConnection openConnection(URL url) {
+ return new BytesURLConnection(url, content);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealm.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealm.java
new file mode 100644
index 0000000000..0469a480d3
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealm.java
@@ -0,0 +1,128 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+
+Copyright 2002 (C) The Werken Company. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "classworlds" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of The Werken Company. For written permission,
+ please contact bob@werken.com.
+
+4. Products derived from this Software may not be called "classworlds"
+ nor may "classworlds" appear in their names without prior written
+ permission of The Werken Company. "classworlds" is a registered
+ trademark of The Werken Company.
+
+5. Due credit should be given to The Werken Company.
+ (http://classworlds.werken.com/).
+
+THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * <p>Autonomous sub-portion of a <code>ClassWorld</code>.</p>
+ *
+ * <p>This class most closed maps to the <code>ClassLoader</code>
+ * role from Java and in facts can provide a <code>ClassLoader</code>
+ * view of itself using {@link #getClassLoader}.</p>
+ *
+ * <p>This is a legacy interface from Classworlds 1.x, preserved for binary compatibility.
+ * The compiled bytecode of {@code org.eclipse.sisu:org.eclipse.sisu.plexus} references this
+ * interface directly. Removing it breaks all Maven 3+ builds.
+ * New code should use {@link org.codehaus.plexus.classworlds.realm.ClassRealm}.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.realm.ClassRealm} for new code.
+ * This interface must remain on the classpath for Sisu compatibility.
+ */
+@SuppressWarnings("rawtypes")
+@Deprecated
+public interface ClassRealm {
+ String getId();
+
+ ClassWorld getWorld();
+
+ void importFrom(String realmId, String pkgName) throws NoSuchRealmException;
+
+ void addConstituent(URL constituent);
+
+ ClassRealm locateSourceRealm(String className);
+
+ void setParent(ClassRealm classRealm);
+
+ ClassRealm createChildRealm(String id) throws DuplicateRealmException;
+
+ ClassLoader getClassLoader();
+
+ ClassRealm getParent();
+
+ URL[] getConstituents();
+
+ // ----------------------------------------------------------------------
+ // Classloading
+ // ----------------------------------------------------------------------
+
+ Class loadClass(String name) throws ClassNotFoundException;
+
+ // ----------------------------------------------------------------------
+ // Resource handling
+ // ----------------------------------------------------------------------
+
+ URL getResource(String name);
+
+ Enumeration findResources(String name) throws IOException;
+
+ InputStream getResourceAsStream(String name);
+
+ void display();
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmAdapter.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmAdapter.java
new file mode 100644
index 0000000000..0a6c45f91e
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmAdapter.java
@@ -0,0 +1,163 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * Adapter that wraps a modern {@link org.codehaus.plexus.classworlds.realm.ClassRealm} and exposes
+ * it as the legacy {@link ClassRealm} interface.
+ *
+ * <p>This class is referenced directly by the compiled bytecode of
+ * {@code org.eclipse.sisu:org.eclipse.sisu.plexus} and must remain on the classpath for
+ * Maven 3+ to function. Do not remove or rename it. New code should not use this adapter;
+ * use {@link org.codehaus.plexus.classworlds.realm.ClassRealm} directly.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated Legacy adapter retained for Sisu binary compatibility.
+ */
+@SuppressWarnings({"UnnecessaryLocalVariable", "DeprecatedIsStillUsed", "rawtypes"})
+@Deprecated
+public class ClassRealmAdapter implements ClassRealm {
+
+ public static ClassRealmAdapter getInstance(org.codehaus.plexus.classworlds.realm.ClassRealm newRealm) {
+ ClassRealmAdapter adapter = new ClassRealmAdapter(newRealm);
+ return adapter;
+ }
+
+ private final org.codehaus.plexus.classworlds.realm.ClassRealm realm;
+
+ private ClassRealmAdapter(org.codehaus.plexus.classworlds.realm.ClassRealm newRealm) {
+ this.realm = newRealm;
+ }
+
+ public String getId() {
+ return realm.getId();
+ }
+
+ public ClassWorld getWorld() {
+ return ClassWorldAdapter.getInstance(realm.getWorld());
+ }
+
+ public void importFrom(String realmId, String pkgName) throws NoSuchRealmException {
+ try {
+ realm.importFrom(realmId, pkgName);
+ } catch (org.codehaus.plexus.classworlds.realm.NoSuchRealmException e) {
+ throw new NoSuchRealmException(getWorld(), e.getId());
+ }
+ }
+
+ public void addConstituent(URL constituent) {
+ realm.addURL(constituent);
+ }
+
+ public ClassRealm locateSourceRealm(String className) {
+ ClassLoader importLoader = realm.getImportClassLoader(className);
+ if (importLoader instanceof org.codehaus.plexus.classworlds.realm.ClassRealm) {
+ return ClassRealmAdapter.getInstance((org.codehaus.plexus.classworlds.realm.ClassRealm) importLoader);
+ } else {
+ return null;
+ }
+ }
+
+ public void setParent(ClassRealm classRealm) {
+ if (classRealm != null) {
+ realm.setParentRealm(ClassRealmReverseAdapter.getInstance(classRealm));
+ }
+ }
+
+ public ClassRealm createChildRealm(String id) throws DuplicateRealmException {
+ try {
+ return ClassRealmAdapter.getInstance(realm.createChildRealm(id));
+ } catch (org.codehaus.plexus.classworlds.realm.DuplicateRealmException e) {
+ throw new DuplicateRealmException(getWorld(), e.getId());
+ }
+ }
+
+ public ClassLoader getClassLoader() {
+ return realm;
+ }
+
+ public ClassRealm getParent() {
+ return ClassRealmAdapter.getInstance(realm.getParentRealm());
+ }
+
+ public ClassRealm getParentRealm() {
+ return ClassRealmAdapter.getInstance(realm.getParentRealm());
+ }
+
+ public URL[] getConstituents() {
+ return realm.getURLs();
+ }
+
+ public Class loadClass(String name) throws ClassNotFoundException {
+ return realm.loadClass(name);
+ }
+
+ public URL getResource(String name) {
+ return realm.getResource(trimLeadingSlash(name));
+ }
+
+ public Enumeration findResources(String name) throws IOException {
+ return realm.findResources(trimLeadingSlash(name));
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return realm.getResourceAsStream(trimLeadingSlash(name));
+ }
+
+ public void display() {
+ realm.display();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof ClassRealm)) return false;
+ return getId().equals(((ClassRealm) o).getId());
+ }
+
+ /**
+ * Provides backward-compat with the old classworlds which accepted resource names with leading slashes.
+ */
+ private String trimLeadingSlash(String resource) {
+ if (resource != null && resource.startsWith("/")) {
+ return resource.substring(1);
+ } else {
+ return resource;
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmReverseAdapter.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmReverseAdapter.java
new file mode 100644
index 0000000000..f0dfc2ab8e
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmReverseAdapter.java
@@ -0,0 +1,141 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * A reverse adapter for ClassRealms.
+ *
+ * <p><b>Note:</b> This is a legacy internal class provided for backward compatibility with Maven 2.
+ * New code should avoid using this adapter.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated This is a legacy internal class.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+@Deprecated
+public class ClassRealmReverseAdapter extends org.codehaus.plexus.classworlds.realm.ClassRealm {
+
+ public static ClassRealmReverseAdapter getInstance(ClassRealm oldRealm) {
+ return new ClassRealmReverseAdapter(oldRealm);
+ }
+
+ private final ClassRealm realm;
+
+ private ClassRealmReverseAdapter(ClassRealm oldRealm) {
+ super(ClassWorldReverseAdapter.getInstance(oldRealm.getWorld()), oldRealm.getId(), oldRealm.getClassLoader());
+ this.realm = oldRealm;
+ }
+
+ public String getId() {
+ return realm.getId();
+ }
+
+ public org.codehaus.plexus.classworlds.ClassWorld getWorld() {
+ return ClassWorldReverseAdapter.getInstance(realm.getWorld());
+ }
+
+ public void importFrom(String realmId, String pkgName)
+ throws org.codehaus.plexus.classworlds.realm.NoSuchRealmException {
+ try {
+ realm.importFrom(realmId, pkgName);
+ } catch (NoSuchRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.NoSuchRealmException(getWorld(), e.getId());
+ }
+ }
+
+ public void addURL(URL constituent) {
+ realm.addConstituent(constituent);
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm locateSourceRealm(String className) {
+ return getInstance(realm.locateSourceRealm(className));
+ }
+
+ public void setParentRealm(org.codehaus.plexus.classworlds.realm.ClassRealm classRealm) {
+ realm.setParent(ClassRealmAdapter.getInstance(classRealm));
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm createChildRealm(String id)
+ throws org.codehaus.plexus.classworlds.realm.DuplicateRealmException {
+ try {
+ return getInstance(realm.createChildRealm(id));
+ } catch (DuplicateRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.DuplicateRealmException(getWorld(), e.getId());
+ }
+ }
+
+ public ClassLoader getClassLoader() {
+ return realm.getClassLoader();
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm getParentRealm() {
+ return getInstance(realm.getParent());
+ }
+
+ public URL[] getURLs() {
+ return realm.getConstituents();
+ }
+
+ public Class loadClass(String name) throws ClassNotFoundException {
+ return realm.loadClass(name);
+ }
+
+ public URL getResource(String name) {
+ return realm.getResource(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Enumeration findResources(String name) throws IOException {
+ return realm.findResources(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return realm.getResourceAsStream(name);
+ }
+
+ public void display() {
+ realm.display();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof ClassRealm)) return false;
+ return getId().equals(((ClassRealm) o).getId());
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorld.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorld.java
new file mode 100644
index 0000000000..cdc0195798
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorld.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.util.Collection;
+
+/**
+ * A compatibility wrapper for {@link org.codehaus.plexus.classworlds.ClassWorld}
+ * provided for legacy code.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.ClassWorld}.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.ClassWorld}
+ */
+@SuppressWarnings("rawtypes")
+@Deprecated
+public class ClassWorld {
+ private ClassWorldAdapter adapter;
+
+ public ClassWorld(String realmId, ClassLoader classLoader) {
+ adapter = ClassWorldAdapter.getInstance(new org.codehaus.plexus.classworlds.ClassWorld(realmId, classLoader));
+ }
+
+ public ClassWorld() {
+ adapter = ClassWorldAdapter.getInstance(new org.codehaus.plexus.classworlds.ClassWorld());
+ }
+
+ public ClassWorld(boolean ignore) {
+ /* fake */
+ }
+
+ public ClassRealm newRealm(String id) throws DuplicateRealmException {
+ return adapter.newRealm(id);
+ }
+
+ public ClassRealm newRealm(String id, ClassLoader classLoader) throws DuplicateRealmException {
+ return adapter.newRealm(id, classLoader);
+ }
+
+ public void disposeRealm(String id) throws NoSuchRealmException {
+ adapter.disposeRealm(id);
+ }
+
+ public ClassRealm getRealm(String id) throws NoSuchRealmException {
+ return adapter.getRealm(id);
+ }
+
+ public Collection getRealms() {
+ return adapter.getRealms();
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldAdapter.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldAdapter.java
new file mode 100644
index 0000000000..e3ff6471b6
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.util.Collection;
+import java.util.Vector;
+
+/**
+ * An adapter for ClassWorlds.
+ *
+ * <p><b>Note:</b> This is a legacy internal class provided for backward compatibility with Maven 2.
+ * New code should avoid using this adapter.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated This is a legacy internal class.
+ */
+@SuppressWarnings("rawtypes")
+@Deprecated
+public class ClassWorldAdapter extends ClassWorld {
+
+ public static ClassWorldAdapter getInstance(org.codehaus.plexus.classworlds.ClassWorld newWorld) {
+ return new ClassWorldAdapter(newWorld);
+ }
+
+ private final org.codehaus.plexus.classworlds.ClassWorld world;
+
+ private ClassWorldAdapter(org.codehaus.plexus.classworlds.ClassWorld newWorld) {
+ super(false);
+ this.world = newWorld;
+ }
+
+ public ClassRealm newRealm(String id) throws DuplicateRealmException {
+ try {
+ return ClassRealmAdapter.getInstance(world.newRealm(id));
+ } catch (org.codehaus.plexus.classworlds.realm.DuplicateRealmException e) {
+ throw new DuplicateRealmException(this, e.getId());
+ }
+ }
+
+ public ClassRealm newRealm(String id, ClassLoader classLoader) throws DuplicateRealmException {
+ try {
+ return ClassRealmAdapter.getInstance(world.newRealm(id, classLoader));
+ } catch (org.codehaus.plexus.classworlds.realm.DuplicateRealmException e) {
+ throw new DuplicateRealmException(this, e.getId());
+ }
+ }
+
+ public void disposeRealm(String id) throws NoSuchRealmException {
+ try {
+ world.disposeRealm(id);
+ } catch (org.codehaus.plexus.classworlds.realm.NoSuchRealmException e) {
+ throw new NoSuchRealmException(this, e.getId());
+ }
+ }
+
+ public ClassRealm getRealm(String id) throws NoSuchRealmException {
+ try {
+ return ClassRealmAdapter.getInstance(world.getRealm(id));
+ } catch (org.codehaus.plexus.classworlds.realm.NoSuchRealmException e) {
+ throw new NoSuchRealmException(this, e.getId());
+ }
+ }
+
+ public Collection getRealms() {
+ Collection<org.codehaus.plexus.classworlds.realm.ClassRealm> realms = world.getRealms();
+ Vector<ClassRealmAdapter> ret = new Vector<>();
+ for (org.codehaus.plexus.classworlds.realm.ClassRealm classRealm : realms) {
+ ret.add(ClassRealmAdapter.getInstance(classRealm));
+ }
+ return ret;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldException.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldException.java
new file mode 100644
index 0000000000..76f51e3e7a
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldException.java
@@ -0,0 +1,122 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+
+Copyright 2002 (C) The Werken Company. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "classworlds" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of The Werken Company. For written permission,
+ please contact bob@werken.com.
+
+4. Products derived from this Software may not be called "classworlds"
+ nor may "classworlds" appear in their names without prior written
+ permission of The Werken Company. "classworlds" is a registered
+ trademark of The Werken Company.
+
+5. Due credit should be given to The Werken Company.
+ (http://classworlds.werken.com/).
+
+THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+/**
+ * Base exception for <code>ClassWorld</code> errors.
+ *
+ * <p><b>Note:</b> This is a legacy exception provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.ClassWorldException}.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.ClassWorldException}
+ */
+@Deprecated
+public class ClassWorldException extends Exception {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The world.
+ */
+ private final ClassWorld world;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ */
+ public ClassWorldException(final ClassWorld world) {
+ this.world = world;
+ }
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param msg The detail message.
+ */
+ public ClassWorldException(final ClassWorld world, final String msg) {
+ super(msg);
+ this.world = world;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the world.
+ *
+ * @return The world.
+ */
+ public ClassWorld getWorld() {
+ return this.world;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldReverseAdapter.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldReverseAdapter.java
new file mode 100644
index 0000000000..7429f73591
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldReverseAdapter.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Vector;
+
+/**
+ * A reverse adapter for ClassWorlds.
+ *
+ * <p><b>Note:</b> This is a legacy internal class provided for backward compatibility with Maven 2.
+ * New code should avoid using this adapter.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated This is a legacy internal class.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+@Deprecated
+public class ClassWorldReverseAdapter extends org.codehaus.plexus.classworlds.ClassWorld {
+ private static final HashMap instances = new HashMap();
+
+ public static ClassWorldReverseAdapter getInstance(ClassWorld oldWorld) {
+ if (instances.containsKey(oldWorld)) return (ClassWorldReverseAdapter) instances.get(oldWorld);
+ ClassWorldReverseAdapter adapter = new ClassWorldReverseAdapter(oldWorld);
+ instances.put(oldWorld, adapter);
+ return adapter;
+ }
+
+ private final ClassWorld world;
+
+ private ClassWorldReverseAdapter(ClassWorld newWorld) {
+ super();
+ this.world = newWorld;
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm newRealm(String id)
+ throws org.codehaus.plexus.classworlds.realm.DuplicateRealmException {
+ try {
+ return ClassRealmReverseAdapter.getInstance(world.newRealm(id));
+ } catch (DuplicateRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.DuplicateRealmException(this, e.getId());
+ }
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm newRealm(String id, ClassLoader classLoader)
+ throws org.codehaus.plexus.classworlds.realm.DuplicateRealmException {
+ try {
+ return ClassRealmReverseAdapter.getInstance(world.newRealm(id, classLoader));
+ } catch (DuplicateRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.DuplicateRealmException(this, e.getId());
+ }
+ }
+
+ public void disposeRealm(String id) throws org.codehaus.plexus.classworlds.realm.NoSuchRealmException {
+ try {
+ world.disposeRealm(id);
+ } catch (NoSuchRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.NoSuchRealmException(this, e.getId());
+ }
+ }
+
+ public org.codehaus.plexus.classworlds.realm.ClassRealm getRealm(String id)
+ throws org.codehaus.plexus.classworlds.realm.NoSuchRealmException {
+ try {
+ return ClassRealmReverseAdapter.getInstance(world.getRealm(id));
+ } catch (NoSuchRealmException e) {
+ throw new org.codehaus.plexus.classworlds.realm.NoSuchRealmException(this, e.getId());
+ }
+ }
+
+ public Collection getRealms() {
+ Collection realms = world.getRealms();
+ Vector ret = new Vector();
+ for (Object o : realms) {
+ ClassRealm realm = (ClassRealm) o;
+ ret.add(ClassRealmReverseAdapter.getInstance(realm));
+ }
+ return ret;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfigurationException.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfigurationException.java
new file mode 100644
index 0000000000..97396ba668
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfigurationException.java
@@ -0,0 +1,96 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+
+Copyright 2002 (C) The Werken Company. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "classworlds" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of The Werken Company. For written permission,
+ please contact bob@werken.com.
+
+4. Products derived from this Software may not be called "classworlds"
+ nor may "classworlds" appear in their names without prior written
+ permission of The Werken Company. "classworlds" is a registered
+ trademark of The Werken Company.
+
+5. Due credit should be given to The Werken Company.
+ (http://classworlds.werken.com/).
+
+THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+/**
+ * Indicates an error during <code>Launcher</code> configuration.
+ *
+ * <p><b>Note:</b> This is a legacy exception provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.launcher.ConfigurationException}.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.launcher.ConfigurationException}
+ */
+@Deprecated
+public class ConfigurationException extends Exception {
+ /**
+ * Construct.
+ *
+ * @param msg The message.
+ */
+ public ConfigurationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct.
+ *
+ * @param msg The message.
+ * @param lineNo The number of configuraton line where the problem occured.
+ * @param line The configuration line where the problem occured.
+ */
+ public ConfigurationException(String msg, int lineNo, String line) {
+ super(msg + " (" + lineNo + "): " + line);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Configurator.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Configurator.java
new file mode 100644
index 0000000000..0853c80b5d
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Configurator.java
@@ -0,0 +1,151 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+
+/**
+ * A compatibility wrapper for {@link org.codehaus.plexus.classworlds.launcher.Configurator}
+ * provided for legacy code.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.launcher.Configurator}.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.launcher.Configurator}
+ */
+@Deprecated
+public class Configurator {
+ private final ConfiguratorAdapter config;
+
+ /** Construct.
+ *
+ * @param launcher The launcher to configure.
+ */
+ public Configurator(Launcher launcher) {
+ config = ConfiguratorAdapter.getInstance(
+ new org.codehaus.plexus.classworlds.launcher.Configurator(launcher), launcher);
+ }
+
+ /** Construct.
+ *
+ * @param world The classWorld to configure.
+ */
+ public Configurator(ClassWorld world) {
+ config = ConfiguratorAdapter.getInstance(
+ new org.codehaus.plexus.classworlds.launcher.Configurator(ClassWorldReverseAdapter.getInstance(world)),
+ world);
+ }
+
+ /** set world.
+ * this setter is provided so you can use the same configurator to configure several "worlds"
+ *
+ * @param world The classWorld to configure.
+ */
+ public void setClassWorld(ClassWorld world) {
+ config.setClassWorld(world);
+ }
+
+ /**
+ * Configure from a file.
+ *
+ * @param is The config input stream
+ * @throws IOException If an error occurs reading the config file.
+ * @throws MalformedURLException If the config file contains invalid URLs.
+ * @throws ConfigurationException If the config file is corrupt.
+ * @throws DuplicateRealmException If the config file defines two realms with the same id.
+ * @throws NoSuchRealmException If the config file defines a main entry point in
+ * a non-existent realm.
+ */
+ public void configure(InputStream is)
+ throws IOException, MalformedURLException, ConfigurationException, DuplicateRealmException,
+ NoSuchRealmException {
+ config.configureAdapter(is);
+ }
+
+ /**
+ * Associate parent realms with their children.
+ */
+ protected void associateRealms() {
+ config.associateRealms();
+ }
+
+ /**
+ * Load a glob into the specified classloader.
+ *
+ * @param line The path configuration line.
+ * @param realm The realm to populate
+ * @throws MalformedURLException If the line does not represent
+ * a valid path element.
+ * @throws FileNotFoundException If the line does not represent
+ * a valid path element in the filesystem.
+ */
+ protected void loadGlob(String line, ClassRealm realm) throws MalformedURLException, FileNotFoundException {
+ loadGlob(line, realm, false);
+ }
+
+ /**
+ * Load a glob into the specified classloader.
+ *
+ * @param line The path configuration line.
+ * @param realm The realm to populate
+ * @param optionally Whether the path is optional or required
+ * @throws MalformedURLException If the line does not represent
+ * a valid path element.
+ * @throws FileNotFoundException If the line does not represent
+ * a valid path element in the filesystem.
+ */
+ @SuppressWarnings("RedundantThrows")
+ protected void loadGlob(String line, ClassRealm realm, boolean optionally)
+ throws MalformedURLException, FileNotFoundException {
+ config.loadGlob(line, realm, optionally);
+ }
+
+ /**
+ * Filter a string for system properties.
+ *
+ * @param text The text to filter.
+ * @return The filtered text.
+ * @throws ConfigurationException If the property does not
+ * exist or if there is a syntax error.
+ */
+ @SuppressWarnings("RedundantThrows")
+ protected String filter(String text) throws ConfigurationException {
+ return config.filter(text);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfiguratorAdapter.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfiguratorAdapter.java
new file mode 100644
index 0000000000..8cd912c801
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfiguratorAdapter.java
@@ -0,0 +1,92 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+
+/**
+ * An adapter for Configurators.
+ *
+ * <p><b>Note:</b> This is a legacy internal class provided for backward compatibility with Maven 2.
+ * New code should avoid using this adapter.</p>
+ *
+ * @deprecated This is a legacy internal class.
+ */
+@Deprecated
+public class ConfiguratorAdapter extends Configurator {
+
+ public static ConfiguratorAdapter getInstance(
+ org.codehaus.plexus.classworlds.launcher.Configurator newConfig, Launcher launcher) {
+ return new ConfiguratorAdapter(newConfig, launcher);
+ }
+
+ public static ConfiguratorAdapter getInstance(
+ org.codehaus.plexus.classworlds.launcher.Configurator newConfig, ClassWorld world) {
+ return new ConfiguratorAdapter(newConfig, world);
+ }
+
+ private final org.codehaus.plexus.classworlds.launcher.Configurator config;
+
+ private ConfiguratorAdapter(org.codehaus.plexus.classworlds.launcher.Configurator config, Launcher launcher) {
+ super(launcher);
+ this.config = config;
+ }
+
+ private ConfiguratorAdapter(org.codehaus.plexus.classworlds.launcher.Configurator config, ClassWorld world) {
+ super(world);
+ this.config = config;
+ }
+
+ public void associateRealms() {
+ config.associateRealms();
+ }
+
+ @SuppressWarnings("DuplicateThrows")
+ public void configureAdapter(InputStream is)
+ throws IOException, MalformedURLException, ConfigurationException, DuplicateRealmException,
+ NoSuchRealmException {
+ try {
+ config.configure(is);
+ } catch (org.codehaus.plexus.classworlds.launcher.ConfigurationException e) {
+ throw new ConfigurationException(e.getMessage());
+ } catch (org.codehaus.plexus.classworlds.realm.DuplicateRealmException e) {
+ throw new DuplicateRealmException(ClassWorldAdapter.getInstance(e.getWorld()), e.getId());
+ } catch (org.codehaus.plexus.classworlds.realm.NoSuchRealmException e) {
+ throw new NoSuchRealmException(ClassWorldAdapter.getInstance(e.getWorld()), e.getId());
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DefaultClassRealm.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DefaultClassRealm.java
new file mode 100644
index 0000000000..6afe78449b
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DefaultClassRealm.java
@@ -0,0 +1,159 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * A compatibility wrapper for {@link org.codehaus.plexus.classworlds.realm.ClassRealm}
+ * provided for legacy code.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.realm.ClassRealm}.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.realm.ClassRealm}
+ */
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+@SuppressWarnings("rawtypes")
+@Deprecated
+public class DefaultClassRealm implements ClassRealm {
+ private final ClassRealmAdapter adapter;
+
+ public DefaultClassRealm(ClassWorld world, String id) {
+ this(world, id, null);
+ }
+
+ public DefaultClassRealm(ClassWorld world, String id, ClassLoader foreignClassLoader) {
+ this.adapter = ClassRealmAdapter.getInstance(new org.codehaus.plexus.classworlds.realm.ClassRealm(
+ ClassWorldReverseAdapter.getInstance(world), id, foreignClassLoader));
+ }
+
+ public URL[] getConstituents() {
+ return adapter.getConstituents();
+ }
+
+ public ClassRealm getParent() {
+ return adapter.getParentRealm();
+ }
+
+ public void setParent(ClassRealm parent) {
+ adapter.setParent(parent);
+ }
+
+ public String getId() {
+ return adapter.getId();
+ }
+
+ public ClassWorld getWorld() {
+ return adapter.getWorld();
+ }
+
+ public void importFrom(String realmId, String packageName) throws NoSuchRealmException {
+ adapter.importFrom(realmId, packageName);
+ }
+
+ public void addConstituent(URL constituent) {
+ adapter.addConstituent(constituent);
+ }
+
+ /**
+ * Adds a byte[] class definition as a constituent for locating classes.
+ * Currently uses BytesURLStreamHandler to hold a reference of the byte[] in memory.
+ * This ensures we have a unifed URL resource model for all constituents.
+ * The code to cache to disk is commented out - maybe a property to choose which method?
+ *
+ * @param constituent class name
+ * @param b the class definition as a byte[]
+ * @throws ClassNotFoundException when class couldn't be loaded
+ */
+ public void addConstituent(String constituent, byte[] b) throws ClassNotFoundException {
+ try {
+ File path, file;
+ if (constituent.lastIndexOf('.') != -1) {
+ path = new File("byteclass/"
+ + constituent
+ .substring(0, constituent.lastIndexOf('.') + 1)
+ .replace('.', File.separatorChar));
+ file = new File(path, constituent.substring(constituent.lastIndexOf('.') + 1) + ".class");
+ } else {
+ path = new File("byteclass/");
+ file = new File(path, constituent + ".class");
+ }
+ addConstituent(new URL(null, file.toURI().toURL().toExternalForm(), new BytesURLStreamHandler(b)));
+ } catch (java.io.IOException e) {
+ throw new ClassNotFoundException("Couldn't load byte stream.", e);
+ }
+ }
+
+ public ClassRealm locateSourceRealm(String classname) {
+ return adapter.locateSourceRealm(classname);
+ }
+
+ public ClassLoader getClassLoader() {
+ return adapter.getClassLoader();
+ }
+
+ public ClassRealm createChildRealm(String id) throws DuplicateRealmException {
+ return adapter.createChildRealm(id);
+ }
+
+ // ----------------------------------------------------------------------
+ // ClassLoader API
+ // ----------------------------------------------------------------------
+
+ public Class loadClass(String name) throws ClassNotFoundException {
+ return adapter.loadClass(name);
+ }
+
+ public URL getResource(String name) {
+ return adapter.getResource(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return adapter.getResourceAsStream(name);
+ }
+
+ public Enumeration findResources(String name) throws IOException {
+ return adapter.findResources(name);
+ }
+
+ public void display() {
+ adapter.display();
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DuplicateRealmException.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DuplicateRealmException.java
new file mode 100644
index 0000000000..c139554310
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DuplicateRealmException.java
@@ -0,0 +1,114 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+
+Copyright 2002 (C) The Werken Company. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "classworlds" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of The Werken Company. For written permission,
+ please contact bob@werken.com.
+
+4. Products derived from this Software may not be called "classworlds"
+ nor may "classworlds" appear in their names without prior written
+ permission of The Werken Company. "classworlds" is a registered
+ trademark of The Werken Company.
+
+5. Due credit should be given to The Werken Company.
+ (http://classworlds.werken.com/).
+
+THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+/**
+ * Indicates an attempt to add a <code>ClassRealm</code> to a
+ * <code>ClassWorld</code> with a duplicate id.
+ *
+ * <p><b>Note:</b> This is a legacy exception provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.realm.DuplicateRealmException}.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.realm.DuplicateRealmException}
+ */
+@Deprecated
+public class DuplicateRealmException extends ClassWorldException {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The realm id.
+ */
+ private final String id;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param id The realm id.
+ */
+ public DuplicateRealmException(ClassWorld world, String id) {
+ super(world, id);
+ this.id = id;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the duplicate realm id.
+ *
+ * @return The id.
+ */
+ public String getId() {
+ return this.id;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Launcher.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Launcher.java
new file mode 100644
index 0000000000..d181dbbc90
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Launcher.java
@@ -0,0 +1,76 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * A compatibility wrapper for {@link org.codehaus.plexus.classworlds.launcher.Launcher}
+ * provided for legacy code.
+ *
+ * <p><b>Note:</b> This is a legacy class provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.launcher.Launcher}.</p>
+ *
+ * @author Andrew Williams
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.launcher.Launcher}
+ */
+@Deprecated
+public class Launcher extends org.codehaus.plexus.classworlds.launcher.Launcher {
+ public Launcher() {}
+
+ // ------------------------------------------------------------
+ // Class methods
+ // ------------------------------------------------------------
+
+ /**
+ * Launch the launcher from the command line.
+ * Will exit using System.exit with an exit code of 0 for success, 100 if there was an unknown exception,
+ * or some other code for an application error.
+ *
+ * @param args The application command-line arguments.
+ */
+ public static void main(String[] args) {
+ org.codehaus.plexus.classworlds.launcher.Launcher.main(args);
+ }
+
+ /**
+ * Launch the launcher.
+ *
+ * @param args The application command-line arguments.
+ * @return an integer exit code
+ * @throws Exception If an error occurs.
+ */
+ public static int mainWithExitCode(String[] args) throws Exception {
+ return org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(args);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/NoSuchRealmException.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/NoSuchRealmException.java
new file mode 100644
index 0000000000..065cd9cec4
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/NoSuchRealmException.java
@@ -0,0 +1,114 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.classworlds;
+
+/*
+
+Copyright 2002 (C) The Werken Company. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "classworlds" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of The Werken Company. For written permission,
+ please contact bob@werken.com.
+
+4. Products derived from this Software may not be called "classworlds"
+ nor may "classworlds" appear in their names without prior written
+ permission of The Werken Company. "classworlds" is a registered
+ trademark of The Werken Company.
+
+5. Due credit should be given to The Werken Company.
+ (http://classworlds.werken.com/).
+
+THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+/**
+ * Indicates an attempt to retrieve a <code>ClassRealm</code> from a
+ * <code>ClassWorld</code> with an invalid id.
+ *
+ * <p><b>Note:</b> This is a legacy exception provided for backward compatibility with Maven 2.
+ * New code should use {@link org.codehaus.plexus.classworlds.realm.NoSuchRealmException}.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @deprecated Use {@link org.codehaus.plexus.classworlds.realm.NoSuchRealmException}
+ */
+@Deprecated
+public class NoSuchRealmException extends ClassWorldException {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The realm id.
+ */
+ private final String id;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param id The realm id.
+ */
+ public NoSuchRealmException(ClassWorld world, String id) {
+ super(world, id);
+ this.id = id;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the invalid realm id.
+ *
+ * @return The id.
+ */
+ public String getId() {
+ return this.id;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/package-info.java b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/package-info.java
new file mode 100644
index 0000000000..e94806c4c7
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/classworlds/package-info.java
@@ -0,0 +1,37 @@
+/*
+ * 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
+ *
+ * http://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.
+ */
+/**
+ * Legacy Classworlds 1.x API, preserved for binary compatibility with Eclipse Sisu.
+ *
+ * <p>This package has been deprecated since version 2.5.2 (2014). New code should use
+ * {@code org.codehaus.plexus.classworlds} instead.</p>
+ *
+ * <p><strong>Do not remove this package.</strong> The compiled bytecode of
+ * {@code org.eclipse.sisu:org.eclipse.sisu.plexus} references {@link ClassRealm},
+ * {@link ClassRealmAdapter}, and {@link ClassRealmReverseAdapter} from this package.
+ * Removing them causes {@code ClassNotFoundException} at runtime for any application
+ * using Sisu, including all Maven 3+ builds.</p>
+ *
+ * <p>PR #141 removed this package and had to be reverted immediately (commit 223416e).
+ * See {@code COMPATIBILITY.md} for details on what Sisu references and what preconditions
+ * must be satisfied before any future removal.</p>
+ *
+ * @deprecated Use {@code org.codehaus.plexus.classworlds} for new code.
+ */
+package org.codehaus.classworlds;
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
new file mode 100644
index 0000000000..f341a9b4a8
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
@@ -0,0 +1,305 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.FilteredClassRealm;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+
+/**
+ * A collection of <code>ClassRealm</code>s, indexed by id.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class ClassWorld implements Closeable {
+
+ private static final System.Logger LOGGER = System.getLogger(ClassWorld.class.getName());
+
+ private Map<String, ClassRealm> realms;
+
+ private final List<ClassWorldListener> listeners = new ArrayList<>();
+
+ private ModuleLayer moduleLayer;
+
+ private ModuleLayer.Controller moduleLayerController;
+
+ public ClassWorld(String realmId, ClassLoader classLoader) {
+ this();
+ try {
+ newRealm(realmId, classLoader);
+ } catch (DuplicateRealmException e) {
+ // Will never happen as we are just creating the world.
+ }
+ }
+
+ public ClassWorld() {
+ this.realms = new LinkedHashMap<>();
+ }
+
+ public ClassRealm newRealm(String id) throws DuplicateRealmException {
+ return newRealm(id, getClass().getClassLoader());
+ }
+
+ public ClassRealm newRealm(String id, ClassLoader classLoader) throws DuplicateRealmException {
+ return newRealm(id, classLoader, null);
+ }
+
+ /**
+ * Adds a class realm with filtering.
+ * Only resources/classes whose name matches a given predicate are exposed.
+ * @param id The identifier for this realm, must not be <code>null</code>.
+ * @param classLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
+ * loader.
+ * @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
+ * @return the created class realm
+ * @throws DuplicateRealmException in case a realm with the given id does already exist
+ * @since 2.7.0
+ * @see FilteredClassRealm
+ */
+ public synchronized ClassRealm newRealm(String id, ClassLoader classLoader, Predicate<String> filter)
+ throws DuplicateRealmException {
+ if (realms.containsKey(id)) {
+ throw new DuplicateRealmException(this, id);
+ }
+ ClassRealm realm;
+ if (filter == null) {
+ realm = new ClassRealm(this, id, classLoader);
+ } else {
+ realm = new FilteredClassRealm(filter, this, id, classLoader);
+ }
+ realms.put(id, realm);
+ for (ClassWorldListener listener : listeners) {
+ listener.realmCreated(realm);
+ }
+ return realm;
+ }
+
+ /**
+ * Closes all contained class realms.
+ * @since 2.7.0
+ */
+ @Override
+ public synchronized void close() throws IOException {
+ realms.values().forEach(this::disposeRealm);
+ realms.clear();
+ }
+
+ public synchronized void disposeRealm(String id) throws NoSuchRealmException {
+ ClassRealm realm = realms.remove(id);
+ if (realm != null) {
+ disposeRealm(realm);
+ } else {
+ throw new NoSuchRealmException(this, id);
+ }
+ }
+
+ private void disposeRealm(ClassRealm realm) {
+ try {
+ realm.close();
+ } catch (IOException ignore) {
+ }
+ for (ClassWorldListener listener : listeners) {
+ listener.realmDisposed(realm);
+ }
+ }
+
+ public synchronized ClassRealm getRealm(String id) throws NoSuchRealmException {
+ if (realms.containsKey(id)) {
+ return realms.get(id);
+ }
+ throw new NoSuchRealmException(this, id);
+ }
+
+ public synchronized Collection<ClassRealm> getRealms() {
+ return Collections.unmodifiableList(new ArrayList<>(realms.values()));
+ }
+
+ public synchronized void setModuleLayer(ModuleLayer moduleLayer, ModuleLayer.Controller controller) {
+ this.moduleLayer = moduleLayer;
+ this.moduleLayerController = controller;
+ }
+
+ public ModuleLayer getModuleLayer() {
+ return moduleLayer;
+ }
+
+ public ModuleLayer.Controller getModuleLayerController() {
+ return moduleLayerController;
+ }
+
+ /**
+ * Exports a package from a named module to the given target module.
+ * Looks up the source module in the runtime layer first, then the boot layer.
+ * Only runtime layer modules can be modified via the Controller; boot layer
+ * modules require {@code --add-exports} in the launcher script.
+ *
+ * @param moduleName the source module name
+ * @param packageName the package to export
+ * @param target the target module to export to
+ */
+ public synchronized void addExports(String moduleName, String packageName, Module target) {
+ applyModuleAccess(moduleName, packageName, target, false);
+ }
+
+ /**
+ * Opens a package from a named module to the given target module for deep reflection.
+ * Looks up the source module in the runtime layer first, then the boot layer.
+ * Only runtime layer modules can be modified via the Controller; boot layer
+ * modules require {@code --add-opens} in the launcher script.
+ *
+ * @param moduleName the source module name
+ * @param packageName the package to open
+ * @param target the target module to open to
+ */
+ public synchronized void addOpens(String moduleName, String packageName, Module target) {
+ applyModuleAccess(moduleName, packageName, target, true);
+ }
+
+ /**
+ * Adds a reads edge from the named source module to the given target module.
+ * Only runtime layer modules can be modified via the Controller.
+ *
+ * @param sourceModuleName the source module name
+ * @param target the target module to read
+ */
+ public synchronized void addReads(String sourceModuleName, Module target) {
+ Module source = findModule(sourceModuleName);
+ if (source == null) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: module ''{0}'' not found, ignoring add-reads",
+ sourceModuleName);
+ return;
+ }
+ if (moduleLayerController == null) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: no ModuleLayer controller available, cannot add-reads {0}",
+ sourceModuleName);
+ return;
+ }
+ if (isBootLayerModule(source)) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: module ''{0}'' is in the boot layer and cannot be modified at runtime;"
+ + " use --add-reads {0}=ALL-UNNAMED on the JVM command line instead",
+ sourceModuleName);
+ return;
+ }
+ moduleLayerController.addReads(source, target);
+ }
+
+ private void applyModuleAccess(String moduleName, String packageName, Module target, boolean open) {
+ String directive = open ? "add-opens" : "add-exports";
+ Module source = findModule(moduleName);
+ if (source == null) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: module ''{0}'' not found, ignoring {1} {0}/{2}",
+ moduleName,
+ directive,
+ packageName);
+ return;
+ }
+ if (moduleLayerController == null) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: no ModuleLayer controller available, cannot {0} {1}/{2}",
+ directive,
+ moduleName,
+ packageName);
+ return;
+ }
+ if (isBootLayerModule(source)) {
+ LOGGER.log(
+ System.Logger.Level.WARNING,
+ "module-access: module ''{0}'' is in the boot layer and cannot be modified at runtime;"
+ + " use --{1} {0}/{2}=ALL-UNNAMED on the JVM command line instead",
+ moduleName,
+ directive,
+ packageName);
+ return;
+ }
+ if (open) {
+ moduleLayerController.addOpens(source, packageName, target);
+ } else {
+ moduleLayerController.addExports(source, packageName, target);
+ }
+ }
+
+ private Module findModule(String moduleName) {
+ if (moduleLayer != null) {
+ Module m = moduleLayer.findModule(moduleName).orElse(null);
+ if (m != null) {
+ return m;
+ }
+ }
+ return ModuleLayer.boot().findModule(moduleName).orElse(null);
+ }
+
+ private boolean isBootLayerModule(Module module) {
+ return module.getLayer() == ModuleLayer.boot();
+ }
+
+ // from exports branch
+ public synchronized ClassRealm getClassRealm(String id) {
+ if (realms.containsKey(id)) {
+ return realms.get(id);
+ }
+ return null;
+ }
+
+ public synchronized void addListener(ClassWorldListener listener) {
+ // TODO ideally, use object identity, not equals
+ if (!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+
+ public synchronized void removeListener(ClassWorldListener listener) {
+ listeners.remove(listener);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldException.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldException.java
new file mode 100644
index 0000000000..72f7f52b1e
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldException.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * Base exception for <code>ClassWorld</code> errors.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class ClassWorldException extends Exception {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The world.
+ */
+ private ClassWorld world;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ */
+ public ClassWorldException(final ClassWorld world) {
+ this.world = world;
+ }
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param msg The detail message.
+ */
+ public ClassWorldException(final ClassWorld world, final String msg) {
+ super(msg);
+ this.world = world;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the world.
+ *
+ * @return The world.
+ */
+ public ClassWorld getWorld() {
+ return this.world;
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldListener.java
similarity index 52%
copy from its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
copy to impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldListener.java
index edb262ce2d..e181cf397d 100644
--- a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldListener.java
@@ -16,14 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.maven.its.core_extensions;
+package org.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
-import org.apache.maven.classrealm.ClassRealmManagerDelegate;
-import org.apache.maven.classrealm.ClassRealmRequest;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
-import org.codehaus.plexus.component.annotations.Component;
-@Component(role = ClassRealmManagerDelegate.class, hint = "TestClassRealmManagerDelegate")
-public class TestClassRealmManagerDelegate implements ClassRealmManagerDelegate {
- public void setupRealm(ClassRealm classRealm, ClassRealmRequest request) {}
+public interface ClassWorldListener {
+ void realmCreated(ClassRealm realm);
+
+ void realmDisposed(ClassRealm realm);
}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/UrlUtils.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/UrlUtils.java
new file mode 100644
index 0000000000..32ed616a5e
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/UrlUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
+ */
+public class UrlUtils {
+ public static String normalizeUrlPath(String name) {
+ if (name.startsWith("/")) {
+ name = name.substring(1);
+ }
+ // Looking for org/codehaus/werkflow/personality/basic/../common/core-idioms.xml
+ // | i |
+ // +-------+ remove
+ //
+ int i = name.indexOf("/..");
+ // Can't be at the beginning because we have no root to refer to so
+ // we start at 1.
+ if (i > 0) {
+ int j = name.lastIndexOf("/", i - 1);
+ if (j >= 0) {
+ name = name.substring(0, j) + name.substring(i + 3);
+ }
+ }
+ return name;
+ }
+
+ public static Set<URL> getURLs(URLClassLoader loader) {
+ return new HashSet<>(Arrays.asList(loader.getURLs()));
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationException.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationException.java
new file mode 100644
index 0000000000..4eb4b57570
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationException.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * Indicates an error during <code>Launcher</code> configuration.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class ConfigurationException extends Exception {
+ /**
+ * Construct.
+ *
+ * @param msg The message.
+ */
+ public ConfigurationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct.
+ *
+ * @param msg The message.
+ * @param lineNo The number of configuration line where the problem occurred.
+ * @param line The configuration line where the problem occurred.
+ */
+ public ConfigurationException(String msg, int lineNo, String line) {
+ super(msg + " (" + lineNo + "): " + line);
+ }
+
+ protected ConfigurationException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationHandler.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationHandler.java
new file mode 100644
index 0000000000..628055b525
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationHandler.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.File;
+import java.net.URL;
+
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+
+/**
+ * Receive notification of the logical content of launcher configuration, independently from parsing.
+ *
+ * @author Igor Fedorenko
+ */
+public interface ConfigurationHandler {
+
+ /**
+ * Define the main class name
+ * @param mainClassName the main class name
+ * @param mainRealmName the main realm from which the main class is loaded
+ */
+ void setAppMain(String mainClassName, String mainRealmName);
+
+ /**
+ * Define a new realm
+ * @param realmName the new realm name
+ * @throws DuplicateRealmException when realm with name already exists
+ */
+ void addRealm(String realmName) throws DuplicateRealmException;
+
+ /**
+ * Add an import specification from a realm
+ * @param realmName the realm name
+ * @param importSpec the import specification
+ * @throws NoSuchRealmException if realm doesn't exist
+ */
+ void addImportFrom(String realmName, String importSpec) throws NoSuchRealmException;
+
+ /**
+ * Add a file to the realm
+ * @param file the file to load content from
+ */
+ void addLoadFile(File file);
+
+ /**
+ * Add an URL to the realm
+ * @param url the url to load content from
+ */
+ void addLoadURL(URL url);
+
+ /**
+ * Add a file to the module path. Jars added via this method will be loaded
+ * as JPMS named modules in a {@link ModuleLayer}, rather than on the classpath.
+ * @param file the jar file to add to the module path
+ */
+ default void addModuleFile(File file) {}
+
+ /**
+ * Export a package from a module to a target module.
+ * @param module the source module name
+ * @param pkg the package to export
+ * @param target the target module name, or {@code ALL-UNNAMED} for the unnamed module
+ */
+ default void addExports(String module, String pkg, String target) {}
+
+ /**
+ * Open a package from a module to a target module for deep reflection.
+ * @param module the source module name
+ * @param pkg the package to open
+ * @param target the target module name, or {@code ALL-UNNAMED} for the unnamed module
+ */
+ default void addOpens(String module, String pkg, String target) {}
+
+ /**
+ * Add a reads edge from a source module to a target module.
+ * @param source the source module name
+ * @param target the target module name
+ */
+ default void addReads(String source, String target) {}
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParser.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParser.java
new file mode 100644
index 0000000000..22b8604c8b
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParser.java
@@ -0,0 +1,492 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+
+/**
+ * Event based launcher configuration parser, delegating effective configuration handling to ConfigurationHandler.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @author Jason van Zyl
+ * @author Igor Fedorenko
+ * @see ConfigurationHandler
+ */
+public class ConfigurationParser {
+ public static final String MAIN_PREFIX = "main is";
+
+ public static final String SET_PREFIX = "set";
+
+ public static final String IMPORT_PREFIX = "import";
+
+ public static final String LOAD_PREFIX = "load";
+
+ public static final String MODULE_PREFIX = "module";
+
+ public static final String ADD_EXPORTS_PREFIX = "add-exports";
+
+ public static final String ADD_OPENS_PREFIX = "add-opens";
+
+ public static final String ADD_READS_PREFIX = "add-reads";
+
+ /**
+ * Optionally spec prefix.
+ */
+ public static final String OPTIONALLY_PREFIX = "optionally";
+
+ protected static final String FROM_SEPARATOR = " from ";
+
+ protected static final String USING_SEPARATOR = " using ";
+
+ protected static final String DEFAULT_SEPARATOR = " default ";
+
+ private final ConfigurationHandler handler;
+
+ private final Properties systemProperties;
+
+ public ConfigurationParser(ConfigurationHandler handler, Properties systemProperties) {
+ this.handler = handler;
+ this.systemProperties = systemProperties;
+ }
+
+ /**
+ * Parse launcher configuration file and send events to the handler.
+ *
+ * @param is the inputstream
+ * @throws IOException when IOException occurs
+ * @throws ConfigurationException when ConfigurationException occurs
+ * @throws DuplicateRealmException when realm already exists
+ * @throws NoSuchRealmException when realm doesn't exist
+ */
+ public void parse(InputStream is)
+ throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ String line;
+ int lineNo = 0;
+ boolean mainSet = false;
+ String curRealm = null;
+ while (true) {
+ line = reader.readLine();
+ if (line == null) {
+ break;
+ }
+ ++lineNo;
+ line = line.trim();
+ if (canIgnore(line)) {
+ continue;
+ }
+ char lineFirstChar = line.charAt(0);
+ switch (lineFirstChar) {
+ case 'm':
+ if (line.startsWith(MODULE_PREFIX)) {
+ handleModuleConfiguration(line, lineNo);
+ } else {
+ mainSet = handleMainConfiguration(line, lineNo, mainSet);
+ }
+ break;
+ case 's':
+ if (handleSetConfiguration(line, lineNo)) {
+ continue;
+ }
+ break;
+ case '[':
+ curRealm = handleRealmConfiguration(line, lineNo);
+ break;
+ case 'i':
+ handleImportConfiguration(line, lineNo, curRealm);
+ break;
+ case 'l':
+ handleLoadConfiguration(line, lineNo);
+ break;
+ case 'a':
+ handleAddDirective(line, lineNo);
+ break;
+ case 'o':
+ handleOptionallyConfiguration(line, lineNo);
+ break;
+ default:
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load a glob into the specified classloader.
+ *
+ * @param line The path configuration line.
+ * @param optionally Whether the path is optional or required
+ * @throws MalformedURLException If the line does not represent
+ * a valid path element.
+ * @throws FileNotFoundException If the line does not represent
+ * a valid path element in the filesystem.
+ * @throws ConfigurationException will never occur (thrown for backwards compatibility)
+ */
+ protected void loadGlob(String line, boolean optionally)
+ throws MalformedURLException, FileNotFoundException, ConfigurationException {
+ File globFile = new File(line);
+ File dir = globFile.getParentFile();
+ if (!dir.exists()) {
+ if (optionally) {
+ return;
+ } else {
+ throw new FileNotFoundException(dir.toString());
+ }
+ }
+ String localName = globFile.getName();
+ int starLoc = localName.indexOf("*");
+ final String prefix = localName.substring(0, starLoc);
+ final String suffix = localName.substring(starLoc + 1);
+ File[] matches = dir.listFiles((dir1, name) -> {
+ if (!name.startsWith(prefix)) {
+ return false;
+ }
+ if (!name.endsWith(suffix)) {
+ return false;
+ }
+ return true;
+ });
+ for (File match : matches) {
+ handler.addLoadFile(match);
+ }
+ }
+
+ /**
+ * Filter a string for system properties.
+ *
+ * <p>Supports {@code ${name}}, {@code ${name:-default}} (use default when the property is
+ * absent or empty), and {@code ${name:+alternate}} (use alternate when the property is
+ * present and non-empty) syntax.</p>
+ *
+ * @param text The text to filter.
+ * @return The filtered text.
+ * @throws ConfigurationException If a required property does not exist or if there is a
+ * syntax error (unterminated placeholder).
+ */
+ protected String filter(String text) throws ConfigurationException {
+ // Quick check: if no placeholder present, return as-is
+ if (!text.contains("${")) {
+ return text;
+ }
+ // Validate: no unterminated placeholders
+ int start = text.indexOf("${");
+ while (start >= 0) {
+ int stop = text.indexOf("}", start);
+ if (stop < 0) {
+ throw new ConfigurationException("Unterminated property: " + text.substring(start));
+ }
+ start = text.indexOf("${", stop + 1);
+ }
+ try {
+ String result = Interpolator.interpolate(text, name -> {
+ String value = systemProperties.getProperty(name);
+ /* do our best if we are not running from surefire */
+ if (value == null && "basedir".equals(name)) {
+ value = new File("").getAbsolutePath();
+ }
+ return value;
+ });
+ // If any placeholder survived unexpanded (no default, property missing), report it
+ int unresolved = result.indexOf("${");
+ if (unresolved >= 0) {
+ int stop = result.indexOf("}", unresolved);
+ String propName =
+ stop > unresolved ? result.substring(unresolved + 2, stop) : result.substring(unresolved + 2);
+ throw new ConfigurationException("No such property: " + propName);
+ }
+ return result;
+ } catch (IllegalArgumentException e) {
+ throw new ConfigurationException(e.getMessage());
+ }
+ }
+
+ /**
+ * Determine if a line can be ignored because it is
+ * a comment or simply blank.
+ *
+ * @param line The line to test.
+ * @return <code>true</code> if the line is ignorable,
+ * otherwise <code>false</code>.
+ */
+ private boolean canIgnore(String line) {
+ return (line.isEmpty() || line.startsWith("#"));
+ }
+
+ private boolean handleMainConfiguration(String line, int lineNo, boolean mainSet) throws ConfigurationException {
+ if (line.startsWith(MAIN_PREFIX)) {
+ if (mainSet) {
+ throw new ConfigurationException("Duplicate main configuration", lineNo, line);
+ }
+ int fromLoc = line.indexOf(FROM_SEPARATOR, MAIN_PREFIX.length());
+ if (fromLoc < 0) {
+ throw new ConfigurationException("Missing from clause", lineNo, line);
+ }
+ String mainClassName =
+ filter(line.substring(MAIN_PREFIX.length(), fromLoc).trim());
+ String mainRealmName =
+ filter(line.substring(fromLoc + FROM_SEPARATOR.length()).trim());
+ this.handler.setAppMain(mainClassName, mainRealmName);
+ return true;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+
+ private boolean handleSetConfiguration(String line, int lineNo) throws ConfigurationException {
+ if (line.startsWith(SET_PREFIX)) {
+ String conf = line.substring(SET_PREFIX.length()).trim();
+ int usingLoc = conf.indexOf(USING_SEPARATOR);
+ String property = null;
+ String propertiesFileName = null;
+ if (usingLoc >= 0) {
+ property = conf.substring(0, usingLoc).trim();
+ propertiesFileName = filter(
+ conf.substring(usingLoc + USING_SEPARATOR.length()).trim());
+ conf = propertiesFileName;
+ }
+ String defaultValue = null;
+ int defaultLoc = conf.indexOf(DEFAULT_SEPARATOR);
+ if (defaultLoc >= 0) {
+ defaultValue = filter(
+ conf.substring(defaultLoc + DEFAULT_SEPARATOR.length()).trim());
+ if (property == null) {
+ property = conf.substring(0, defaultLoc).trim();
+ } else {
+ propertiesFileName = conf.substring(0, defaultLoc).trim();
+ }
+ }
+ String value = systemProperties.getProperty(property);
+ if (value != null) {
+ return true;
+ }
+ if (propertiesFileName != null) {
+ File propertiesFile = new File(propertiesFileName);
+ if (propertiesFile.exists()) {
+ Properties properties = new Properties();
+ try (InputStream inputStream = Files.newInputStream(Paths.get(propertiesFileName))) {
+ properties.load(inputStream);
+ value = properties.getProperty(property);
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
+ }
+ if (value == null && defaultValue != null) {
+ value = defaultValue;
+ }
+ if (value != null) {
+ value = filter(value);
+ systemProperties.setProperty(property, value);
+ }
+ return false;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+
+ private String handleRealmConfiguration(String line, int lineNo)
+ throws ConfigurationException, DuplicateRealmException {
+ int rbrack = line.indexOf("]");
+ if (rbrack < 0) {
+ throw new ConfigurationException("Invalid realm specifier", lineNo, line);
+ }
+ String realmName = line.substring(1, rbrack);
+ handler.addRealm(realmName);
+ return realmName;
+ }
+
+ private void handleImportConfiguration(String line, int lineNo, String curRealm)
+ throws ConfigurationException, NoSuchRealmException {
+ if (line.startsWith(IMPORT_PREFIX)) {
+ if (curRealm == null) {
+ throw new ConfigurationException("Unhandled import", lineNo, line);
+ }
+ int fromLoc = line.indexOf(FROM_SEPARATOR, IMPORT_PREFIX.length());
+ if (fromLoc < 0) {
+ throw new ConfigurationException("Missing from clause", lineNo, line);
+ }
+ String importSpec = line.substring(IMPORT_PREFIX.length(), fromLoc).trim();
+ String realmName = line.substring(fromLoc + FROM_SEPARATOR.length()).trim();
+ handler.addImportFrom(realmName, importSpec);
+ return;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+
+ private void handleLoadConfiguration(String line, int lineNo)
+ throws ConfigurationException, FileNotFoundException, MalformedURLException {
+ if (line.startsWith(LOAD_PREFIX)) {
+ String constituent = line.substring(LOAD_PREFIX.length()).trim();
+ constituent = filter(constituent);
+ if (constituent.contains("*")) {
+ loadGlob(constituent, false /*not optionally*/);
+ } else {
+ File file = new File(constituent);
+ if (file.exists()) {
+ handler.addLoadFile(file);
+ } else {
+ try {
+ handler.addLoadURL(new URL(constituent));
+ } catch (MalformedURLException e) {
+ throw new FileNotFoundException(constituent);
+ }
+ }
+ }
+ return;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+
+ private void handleModuleConfiguration(String line, int lineNo)
+ throws ConfigurationException, FileNotFoundException, MalformedURLException {
+ if (line.startsWith(MODULE_PREFIX)) {
+ String constituent = line.substring(MODULE_PREFIX.length()).trim();
+ constituent = filter(constituent);
+ if (constituent.contains("*")) {
+ moduleGlob(constituent);
+ } else {
+ File file = new File(constituent);
+ if (file.exists()) {
+ handler.addModuleFile(file);
+ } else {
+ throw new FileNotFoundException(constituent);
+ }
+ }
+ return;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+
+ private void handleAddDirective(String line, int lineNo) throws ConfigurationException {
+ if (line.startsWith(ADD_EXPORTS_PREFIX)) {
+ String spec = line.substring(ADD_EXPORTS_PREFIX.length()).trim();
+ parseModulePackageDirective(spec, lineNo, line, handler::addExports);
+ } else if (line.startsWith(ADD_OPENS_PREFIX)) {
+ String spec = line.substring(ADD_OPENS_PREFIX.length()).trim();
+ parseModulePackageDirective(spec, lineNo, line, handler::addOpens);
+ } else if (line.startsWith(ADD_READS_PREFIX)) {
+ String spec = line.substring(ADD_READS_PREFIX.length()).trim();
+ int eqLoc = spec.indexOf('=');
+ if (eqLoc < 0) {
+ throw new ConfigurationException("Missing '=' in add-reads directive", lineNo, line);
+ }
+ String source = spec.substring(0, eqLoc).trim();
+ String target = spec.substring(eqLoc + 1).trim();
+ handler.addReads(source, target);
+ } else {
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+ }
+
+ private void parseModulePackageDirective(String spec, int lineNo, String line, ModulePackageHandler mph)
+ throws ConfigurationException {
+ int slashLoc = spec.indexOf('/');
+ if (slashLoc < 0) {
+ throw new ConfigurationException("Missing '/' in module/package directive", lineNo, line);
+ }
+ String module = spec.substring(0, slashLoc).trim();
+ String rest = spec.substring(slashLoc + 1).trim();
+ String pkg;
+ String target;
+ int eqLoc = rest.indexOf('=');
+ if (eqLoc >= 0) {
+ pkg = rest.substring(0, eqLoc).trim();
+ target = rest.substring(eqLoc + 1).trim();
+ } else {
+ pkg = rest;
+ target = "ALL-UNNAMED";
+ }
+ mph.handle(module, pkg, target);
+ }
+
+ @FunctionalInterface
+ private interface ModulePackageHandler {
+ void handle(String module, String pkg, String target);
+ }
+
+ protected void moduleGlob(String line) throws MalformedURLException, FileNotFoundException {
+ File globFile = new File(line);
+ File dir = globFile.getParentFile();
+ if (!dir.exists()) {
+ return;
+ }
+ String localName = globFile.getName();
+ int starLoc = localName.indexOf("*");
+ final String prefix = localName.substring(0, starLoc);
+ final String suffix = localName.substring(starLoc + 1);
+ File[] matches = dir.listFiles((dir1, name) -> name.startsWith(prefix) && name.endsWith(suffix));
+ if (matches != null) {
+ for (File match : matches) {
+ handler.addModuleFile(match);
+ }
+ }
+ }
+
+ private void handleOptionallyConfiguration(String line, int lineNo)
+ throws ConfigurationException, FileNotFoundException, MalformedURLException {
+ if (line.startsWith(OPTIONALLY_PREFIX)) {
+ String constituent = line.substring(OPTIONALLY_PREFIX.length()).trim();
+ constituent = filter(constituent);
+ if (constituent.contains("*")) {
+ loadGlob(constituent, true /*optionally*/);
+ } else {
+ File file = new File(constituent);
+ if (file.exists()) {
+ handler.addLoadFile(file);
+ } else {
+ try {
+ handler.addLoadURL(new URL(constituent));
+ } catch (MalformedURLException e) {
+ // swallow
+ }
+ }
+ }
+ return;
+ }
+ throw new ConfigurationException("Unhandled configuration", lineNo, line);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Configurator.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Configurator.java
new file mode 100644
index 0000000000..5701169d11
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Configurator.java
@@ -0,0 +1,357 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.module.Configuration;
+import java.lang.module.FindException;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ResolutionException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+
+/**
+ * <code>Launcher</code> configurator.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @author Jason van Zyl
+ */
+public class Configurator implements ConfigurationHandler {
+ private static final System.Logger LOGGER = System.getLogger(Configurator.class.getName());
+
+ /**
+ * The launcher to configure.
+ */
+ private Launcher launcher;
+
+ private ClassWorld world;
+
+ /**
+ * Processed Realms.
+ */
+ private Map<String, ClassRealm> configuredRealms;
+
+ /**
+ * Current Realm.
+ */
+ private ClassRealm curRealm;
+
+ private ClassLoader foreignClassLoader = null;
+
+ private List<Path> modulePaths = new ArrayList<>();
+
+ /**
+ * Module JAR paths that could not be loaded into a JPMS layer and must be added to the
+ * first realm that gets created instead (used when the {@code module} directive in the
+ * configuration file precedes any {@code [section]} header, so no realm exists yet when
+ * the fallback fires).
+ */
+ private List<Path> pendingModuleClasspathFallback = new ArrayList<>();
+
+ private List<String[]> exportDirectives = new ArrayList<>();
+
+ private List<String[]> openDirectives = new ArrayList<>();
+
+ private List<String[]> readDirectives = new ArrayList<>();
+
+ /**
+ * Construct.
+ *
+ * @param launcher The launcher to configure.
+ */
+ public Configurator(Launcher launcher) {
+ this.launcher = launcher;
+ configuredRealms = new HashMap<>();
+ if (launcher != null) {
+ this.foreignClassLoader = launcher.getSystemClassLoader();
+ }
+ }
+
+ /**
+ * Construct.
+ *
+ * @param world The classWorld to configure.
+ */
+ public Configurator(ClassWorld world) {
+ setClassWorld(world);
+ }
+
+ /**
+ * set world.
+ * this setter is provided so you can use the same configurator to configure several "worlds"
+ *
+ * @param world The classWorld to configure.
+ */
+ public void setClassWorld(ClassWorld world) {
+ this.world = world;
+ configuredRealms = new HashMap<>();
+ }
+
+ /**
+ * Configure from a file.
+ *
+ * @param is The config input stream
+ * @throws IOException If an error occurs reading the config file.
+ * @throws MalformedURLException If the config file contains invalid URLs.
+ * @throws ConfigurationException If the config file is corrupt.
+ * @throws DuplicateRealmException If the config file defines two realms with the same id.
+ * @throws NoSuchRealmException If the config file defines a main entry point in
+ * a non-existent realm.
+ */
+ public void configure(InputStream is)
+ throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException {
+ if (world == null) {
+ world = new ClassWorld();
+ }
+ curRealm = null;
+ foreignClassLoader = null;
+ modulePaths = new ArrayList<>();
+ exportDirectives = new ArrayList<>();
+ openDirectives = new ArrayList<>();
+ readDirectives = new ArrayList<>();
+ if (this.launcher != null) {
+ foreignClassLoader = this.launcher.getSystemClassLoader();
+ }
+ ConfigurationParser parser = new ConfigurationParser(this, System.getProperties());
+ parser.parse(is);
+ createModuleLayer();
+ // Associate child realms to their parents.
+ associateRealms();
+ if (this.launcher != null) {
+ this.launcher.setWorld(world);
+ }
+ }
+
+ // TODO return this to protected when the legacy wrappers can be removed.
+ /**
+ * Associate parent realms with their children.
+ */
+ public void associateRealms() {
+ List<String> sortRealmNames = new ArrayList<>(configuredRealms.keySet());
+ // sort by name
+ sortRealmNames.sort(String::compareTo);
+ // So now we have something like the following for defined
+ // realms:
+ //
+ // root
+ // root.maven
+ // root.maven.plugin
+ //
+ // Now if the name of a realm is a superset of an existing realm
+ // the we want to make child/parent associations.
+ for (String realmName : sortRealmNames) {
+ int j = realmName.lastIndexOf('.');
+ if (j > 0) {
+ String parentRealmName = realmName.substring(0, j);
+ ClassRealm parentRealm = configuredRealms.get(parentRealmName);
+ if (parentRealm != null) {
+ ClassRealm realm = configuredRealms.get(realmName);
+ realm.setParentRealm(parentRealm);
+ }
+ }
+ }
+ }
+
+ public void addImportFrom(String relamName, String importSpec) throws NoSuchRealmException {
+ curRealm.importFrom(relamName, importSpec);
+ }
+
+ public void addLoadFile(File file) {
+ try {
+ curRealm.addURL(file.toURI().toURL());
+ } catch (MalformedURLException e) {
+ // can't really happen... or can it?
+ }
+ }
+
+ public void addLoadURL(URL url) {
+ curRealm.addURL(url);
+ }
+
+ public void addRealm(String realmName) throws DuplicateRealmException {
+ curRealm = world.newRealm(realmName, foreignClassLoader);
+ // If the 'module' directive failed to create a JPMS layer before any section was
+ // declared, drain the deferred classpath fallback into this first realm.
+ if (!pendingModuleClasspathFallback.isEmpty()) {
+ for (Path path : pendingModuleClasspathFallback) {
+ try {
+ curRealm.addURL(path.toUri().toURL());
+ } catch (MalformedURLException e) {
+ // ignore
+ }
+ }
+ pendingModuleClasspathFallback.clear();
+ }
+ // Stash the configured realm for subsequent association processing.
+ configuredRealms.put(realmName, curRealm);
+ }
+
+ public void addModuleFile(File file) {
+ modulePaths.add(file.toPath());
+ }
+
+ public List<Path> getModulePaths() {
+ return modulePaths;
+ }
+
+ public void addExports(String module, String pkg, String target) {
+ exportDirectives.add(new String[] {module, pkg, target});
+ }
+
+ public void addOpens(String module, String pkg, String target) {
+ openDirectives.add(new String[] {module, pkg, target});
+ }
+
+ public void addReads(String source, String target) {
+ readDirectives.add(new String[] {source, target});
+ }
+
+ private void createModuleLayer() {
+ if (modulePaths.isEmpty()
+ && exportDirectives.isEmpty()
+ && openDirectives.isEmpty()
+ && readDirectives.isEmpty()) {
+ return;
+ }
+ if (!modulePaths.isEmpty()) {
+ try {
+ Set<String> bootModuleNames = ModuleLayer.boot().modules().stream()
+ .map(Module::getName)
+ .collect(Collectors.toSet());
+ ModuleFinder finder = ModuleFinder.of(modulePaths.toArray(new Path[0]));
+ Set<String> newModules = finder.findAll().stream()
+ .map(ref -> ref.descriptor().name())
+ .filter(name -> !bootModuleNames.contains(name))
+ .collect(Collectors.toSet());
+ if (!newModules.isEmpty()) {
+ Configuration cf =
+ ModuleLayer.boot().configuration().resolve(finder, ModuleFinder.of(), newModules);
+ ClassLoader parent =
+ foreignClassLoader != null ? foreignClassLoader : ClassLoader.getSystemClassLoader();
+ ModuleLayer.Controller controller =
+ ModuleLayer.defineModulesWithOneLoader(cf, List.of(ModuleLayer.boot()), parent);
+ ModuleLayer layer = controller.layer();
+ ClassLoader oldForeignClassLoader = foreignClassLoader;
+ foreignClassLoader = layer.findLoader(newModules.iterator().next());
+ world.setModuleLayer(layer, controller);
+ // Realms created during parsing (before this module layer existed) used the
+ // old foreignClassLoader as their parent. Retroactively update them so they
+ // delegate to the new module layer loader, making module-layer classes (e.g.
+ // JLine) accessible from the classrealm without duplicating jars on the
+ // classpath.
+ for (ClassRealm realm : configuredRealms.values()) {
+ if (realm.getParentClassLoader() == oldForeignClassLoader) {
+ realm.setParentClassLoader(foreignClassLoader);
+ }
+ }
+ }
+ } catch (FindException | ResolutionException | LayerInstantiationException e) {
+ LOGGER.log(
+ System.Logger.Level.DEBUG,
+ "Failed to create JPMS module layer, falling back to classpath mode: " + e.getMessage());
+ addModulePathsToClasspath();
+ }
+ }
+ Module unnamedTarget = getClass().getModule();
+ for (String[] directive : exportDirectives) {
+ Module target = resolveTargetModule(directive[2], unnamedTarget);
+ if (target != null) {
+ world.addExports(directive[0], directive[1], target);
+ }
+ }
+ for (String[] directive : openDirectives) {
+ Module target = resolveTargetModule(directive[2], unnamedTarget);
+ if (target != null) {
+ world.addOpens(directive[0], directive[1], target);
+ }
+ }
+ for (String[] directive : readDirectives) {
+ Module target = resolveTargetModule(directive[1], unnamedTarget);
+ if (target != null) {
+ world.addReads(directive[0], target);
+ }
+ }
+ }
+
+ private void addModulePathsToClasspath() {
+ if (curRealm != null) {
+ for (Path path : modulePaths) {
+ try {
+ curRealm.addURL(path.toUri().toURL());
+ } catch (MalformedURLException e) {
+ // ignore
+ }
+ }
+ } else {
+ // The 'module' directive appeared before any [section]: no realm exists yet.
+ // Defer adding these JARs to the classpath of the first realm to be created.
+ pendingModuleClasspathFallback.addAll(modulePaths);
+ }
+ }
+
+ private Module resolveTargetModule(String targetName, Module unnamedTarget) {
+ if ("ALL-UNNAMED".equals(targetName)) {
+ return unnamedTarget;
+ }
+ ModuleLayer layer = world.getModuleLayer();
+ if (layer != null) {
+ Module m = layer.findModule(targetName).orElse(null);
+ if (m != null) {
+ return m;
+ }
+ }
+ return ModuleLayer.boot().findModule(targetName).orElse(null);
+ }
+
+ public void setAppMain(String mainClassName, String mainRealmName) {
+ if (this.launcher != null) {
+ this.launcher.setAppMain(mainClassName, mainRealmName);
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Interpolator.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Interpolator.java
new file mode 100644
index 0000000000..5849d67fcd
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Interpolator.java
@@ -0,0 +1,168 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.UnaryOperator;
+
+/**
+ * String interpolator supporting {@code ${var}}, {@code ${var:-default}} and {@code ${var:+alternate}}
+ * syntax. Copied from {@code org.apache.maven.impl.model.DefaultInterpolator} to keep maven-classworlds
+ * free of any maven-impl dependency.
+ */
+class Interpolator {
+
+ private static final char ESCAPE_CHAR = '\\';
+ private static final String DELIM_START = "${";
+ private static final String DELIM_STOP = "}";
+ private static final String MARKER = "$__";
+ private static final String OP_DEFAULT = ":-";
+ private static final String OP_ALTERNATE = ":+";
+
+ private Interpolator() {}
+
+ /**
+ * Interpolates {@code val}, resolving {@code ${name}} placeholders via {@code callback}.
+ * Supports {@code ${var:-default}} (use default when var is absent/empty) and
+ * {@code ${var:+alternate}} (use alternate when var is present and non-empty).
+ *
+ * @param val the string to interpolate
+ * @param callback function that resolves a variable name to its value, or {@code null}
+ * @return the interpolated string
+ */
+ static String interpolate(String val, UnaryOperator<String> callback) {
+ return unescape(doSubstVars(val, null, null, callback));
+ }
+
+ private static String doSubstVars(
+ String val, String currentKey, Set<String> cycleMap, UnaryOperator<String> callback) {
+ if (val == null || val.isEmpty()) {
+ return val;
+ }
+ if (cycleMap == null) {
+ cycleMap = new HashSet<>();
+ }
+ if (currentKey != null) {
+ cycleMap.add(currentKey);
+ }
+ int startDelim;
+ int stopDelim = -1;
+ do {
+ stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+ while (stopDelim > 0 && val.charAt(stopDelim - 1) == ESCAPE_CHAR) {
+ stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+ }
+ startDelim = val.indexOf(DELIM_START);
+ while (stopDelim >= 0) {
+ int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+ if ((idx < 0) || (idx > stopDelim)) {
+ break;
+ } else if (idx < stopDelim) {
+ startDelim = idx;
+ }
+ }
+ } while (startDelim >= 0 && stopDelim >= 0 && stopDelim < startDelim + DELIM_START.length());
+ if ((startDelim < 0) || (stopDelim < 0)) {
+ cycleMap.remove(currentKey);
+ return val;
+ }
+ String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
+ String substValue = processSubstitution(variable, cycleMap, callback);
+ val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length());
+ val = doSubstVars(val, currentKey, cycleMap, callback);
+ cycleMap.remove(currentKey);
+ return val;
+ }
+
+ private static String processSubstitution(String variable, Set<String> cycleMap, UnaryOperator<String> callback) {
+ int startIdx = 0;
+ String substValue = null;
+ while (startIdx < variable.length()) {
+ int idx1 = variable.indexOf(OP_DEFAULT, startIdx);
+ int idx2 = variable.indexOf(OP_ALTERNATE, startIdx);
+ int idx = idx1 >= 0 ? (idx2 >= 0 ? Math.min(idx1, idx2) : idx1) : idx2;
+ if (idx < 0) {
+ if (substValue == null) {
+ substValue = resolveVariable(variable.substring(startIdx), cycleMap, callback);
+ }
+ break;
+ }
+ String varPart = variable.substring(startIdx, idx);
+ if (substValue == null) {
+ substValue = resolveVariable(varPart, cycleMap, callback);
+ }
+ int nextIdx1 = variable.indexOf(OP_DEFAULT, idx + 2);
+ int nextIdx2 = variable.indexOf(OP_ALTERNATE, idx + 2);
+ int nextIdx = nextIdx1 >= 0 ? (nextIdx2 >= 0 ? Math.min(nextIdx1, nextIdx2) : nextIdx1) : nextIdx2;
+ String op = variable.substring(idx, idx + 2);
+ String opValue = variable.substring(idx + 2, nextIdx >= 0 ? nextIdx : variable.length());
+ String processedOpValue = doSubstVars(opValue, variable, cycleMap, callback);
+ if (OP_ALTERNATE.equals(op)) {
+ if (substValue != null && !substValue.isEmpty()) {
+ substValue = processedOpValue;
+ break;
+ }
+ } else {
+ // OP_DEFAULT
+ if (substValue == null || substValue.isEmpty()) {
+ substValue = processedOpValue;
+ break;
+ }
+ }
+ startIdx = nextIdx >= 0 ? nextIdx : variable.length();
+ }
+ // When the variable is unresolvable (no value, no default), preserve the placeholder
+ // so that filter() can detect it and throw ConfigurationException.
+ if (substValue == null) {
+ substValue = MARKER + "{" + variable + "}";
+ }
+ return substValue;
+ }
+
+ private static String resolveVariable(String variable, Set<String> cycleMap, UnaryOperator<String> callback) {
+ if (!cycleMap.add(variable)) {
+ throw new IllegalArgumentException("Recursive variable reference: " + variable);
+ }
+ String value = callback != null ? callback.apply(variable) : null;
+ if (value != null) {
+ value = doSubstVars(value, variable, cycleMap, callback);
+ }
+ cycleMap.remove(variable);
+ return value;
+ }
+
+ private static String unescape(String val) {
+ if (val == null || val.isEmpty()) {
+ return val;
+ }
+ if (val.contains(MARKER)) {
+ val = val.replace(MARKER, "$");
+ }
+ int escape = val.indexOf(ESCAPE_CHAR);
+ while (escape >= 0 && escape < val.length() - 1) {
+ char c = val.charAt(escape + 1);
+ if (c == '{' || c == '}') {
+ val = val.substring(0, escape) + val.substring(escape + 1);
+ }
+ escape = val.indexOf(ESCAPE_CHAR, escape + 1);
+ }
+ return val;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Launcher.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Launcher.java
new file mode 100644
index 0000000000..1dfefc77cd
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/Launcher.java
@@ -0,0 +1,365 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+
+/**
+ * <p>Command-line invokable application launcher.</p>
+ *
+ * <p>This launcher class assists in the creation of classloaders and <code>ClassRealm</code>s
+ * from a configuration file and the launching of the application's <code>main</code>
+ * method from the correct class loaded through the correct classloader.</p>
+ *
+ * <p> The path to the configuration file is specified using the <code>classworlds.conf</code>
+ * system property, typically specified using the <code>-D</code> switch to
+ * <code>java</code>.</p>
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class Launcher {
+ protected static final String CLASSWORLDS_CONF = "classworlds.conf";
+
+ protected static final String UBERJAR_CONF_DIR = "WORLDS-INF/conf/";
+
+ protected ClassLoader systemClassLoader;
+
+ protected String mainClassName;
+
+ protected String mainRealmName;
+
+ protected ClassWorld world;
+
+ private int exitCode = 0;
+
+ public Launcher() {
+ this.systemClassLoader = Thread.currentThread().getContextClassLoader();
+ }
+
+ public void setSystemClassLoader(ClassLoader loader) {
+ this.systemClassLoader = loader;
+ }
+
+ public ClassLoader getSystemClassLoader() {
+ return this.systemClassLoader;
+ }
+
+ public int getExitCode() {
+ return exitCode;
+ }
+
+ public void setAppMain(String mainClassName, String mainRealmName) {
+ this.mainClassName = mainClassName;
+ this.mainRealmName = mainRealmName;
+ }
+
+ public String getMainRealmName() {
+ return this.mainRealmName;
+ }
+
+ public String getMainClassName() {
+ return this.mainClassName;
+ }
+
+ public void setWorld(ClassWorld world) {
+ this.world = world;
+ }
+
+ public ClassWorld getWorld() {
+ return this.world;
+ }
+
+ /**
+ * Configure from a file.
+ *
+ * @param is The config input stream.
+ * @throws IOException If an error occurs reading the config file.
+ * @throws MalformedURLException If the config file contains invalid URLs.
+ * @throws ConfigurationException If the config file is corrupt.
+ * @throws DuplicateRealmException If the config file defines two realms
+ * with the same id.
+ * @throws NoSuchRealmException If the config file defines a main entry
+ * point in a non-existent realm.
+ */
+ public void configure(InputStream is)
+ throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException {
+ Configurator configurator = new Configurator(this);
+ configurator.configure(is);
+ }
+
+ /**
+ * Retrieve the main entry class.
+ *
+ * @return The main entry class.
+ * @throws ClassNotFoundException If the class cannot be found.
+ * @throws NoSuchRealmException If the specified main entry realm does not exist.
+ */
+ public Class<?> getMainClass() throws ClassNotFoundException, NoSuchRealmException {
+ return getMainRealm().loadClass(getMainClassName());
+ }
+
+ /**
+ * Retrieve the main entry realm.
+ *
+ * @return The main entry realm.
+ * @throws NoSuchRealmException If the specified main entry realm does not exist.
+ */
+ public ClassRealm getMainRealm() throws NoSuchRealmException {
+ return getWorld().getRealm(getMainRealmName());
+ }
+
+ /**
+ * Retrieve the enhanced main entry method.
+ *
+ * @return The enhanced main entry method.
+ * @throws ClassNotFoundException If the main entry class cannot be found.
+ * @throws NoSuchMethodException If the main entry method cannot be found.
+ * @throws NoSuchRealmException If the main entry realm cannot be found.
+ */
+ protected Method getEnhancedMainMethod()
+ throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException {
+ Class<?> cwClass = getMainRealm().loadClass(ClassWorld.class.getName());
+ Method m = getMainClass().getMethod("main", String[].class, cwClass);
+ int modifiers = m.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
+ if (m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE) {
+ return m;
+ }
+ }
+ throw new NoSuchMethodException("public static void main(String[] args, ClassWorld world)");
+ }
+
+ /**
+ * Retrieve the main entry method.
+ *
+ * @return The main entry method.
+ * @throws ClassNotFoundException If the main entry class cannot be found.
+ * @throws NoSuchMethodException If the main entry method cannot be found.
+ * @throws NoSuchRealmException If the main entry realm cannot be found.
+ */
+ protected Method getMainMethod() throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException {
+ Method m = getMainClass().getMethod("main", String[].class);
+ int modifiers = m.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
+ if (m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE) {
+ return m;
+ }
+ }
+ throw new NoSuchMethodException("public static void main(String[] args) in " + getMainClass());
+ }
+
+ /**
+ * Launch the application.
+ *
+ * @param args The application args.
+ * @throws ClassNotFoundException If the main entry class cannot be found.
+ * @throws IllegalAccessException If the method cannot be accessed.
+ * @throws InvocationTargetException If the target of the invokation is invalid.
+ * @throws NoSuchMethodException If the main entry method cannot be found.
+ * @throws NoSuchRealmException If the main entry realm cannot be found.
+ */
+ public void launch(String[] args)
+ throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
+ NoSuchRealmException {
+ try {
+ launchEnhanced(args);
+ return;
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ launchStandard(args);
+ }
+
+ /**
+ * <p>Attempt to launch the application through the enhanced main method.</p>
+ *
+ * <p>This will seek a method with the exact signature of:</p>
+ * <pre>
+ * public static void main(String[] args, ClassWorld world)
+ * </pre>
+ *
+ * @param args The application args.
+ * @throws ClassNotFoundException If the main entry class cannot be found.
+ * @throws IllegalAccessException If the method cannot be accessed.
+ * @throws InvocationTargetException If the target of the invokation is
+ * invalid.
+ * @throws NoSuchMethodException If the main entry method cannot be found.
+ * @throws NoSuchRealmException If the main entry realm cannot be found.
+ */
+ protected void launchEnhanced(String[] args)
+ throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
+ NoSuchRealmException {
+ ClassRealm mainRealm = getMainRealm();
+ Class<?> mainClass = getMainClass();
+ Method mainMethod = getEnhancedMainMethod();
+ ClassLoader cl = mainRealm;
+ // ----------------------------------------------------------------------
+ // This is what the classloader for the main realm looks like when we
+ // boot from the command line:
+ // ----------------------------------------------------------------------
+ // [ AppLauncher$AppClassLoader ] : $CLASSPATH envar
+ // ^
+ // |
+ // |
+ // [ AppLauncher$ExtClassLoader ] : ${java.home}/jre/lib/ext/*.jar
+ // ^
+ // |
+ // |
+ // [ Strategy ]
+ // ----------------------------------------------------------------------
+ Thread.currentThread().setContextClassLoader(cl);
+ Object ret = mainMethod.invoke(mainClass, args, getWorld());
+ if (ret instanceof Integer) {
+ exitCode = (Integer) ret;
+ }
+ Thread.currentThread().setContextClassLoader(systemClassLoader);
+ }
+
+ /**
+ * <p>Attempt to launch the application through the standard main method.</p>
+ *
+ * <p>This will seek a method with the exact signature of:</p>
+ *
+ * <pre>
+ * public static void main(String[] args)
+ * </pre>
+ *
+ * @param args The application args.
+ * @throws ClassNotFoundException If the main entry class cannot be found.
+ * @throws IllegalAccessException If the method cannot be accessed.
+ * @throws InvocationTargetException If the target of the invokation is
+ * invalid.
+ * @throws NoSuchMethodException If the main entry method cannot be found.
+ * @throws NoSuchRealmException If the main entry realm cannot be found.
+ */
+ protected void launchStandard(String[] args)
+ throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
+ NoSuchRealmException {
+ ClassRealm mainRealm = getMainRealm();
+ Class<?> mainClass = getMainClass();
+ Method mainMethod = getMainMethod();
+ Thread.currentThread().setContextClassLoader(mainRealm);
+ Object ret = mainMethod.invoke(mainClass, new Object[] {args});
+ if (ret instanceof Integer) {
+ exitCode = (Integer) ret;
+ }
+ Thread.currentThread().setContextClassLoader(systemClassLoader);
+ }
+
+ // ------------------------------------------------------------
+ // Class methods
+ // ------------------------------------------------------------
+
+ /**
+ * Launch the launcher from the command line.
+ * Will exit using System.exit with an exit code of 0 for success, 100 if there was an unknown exception,
+ * or some other code for an application error.
+ *
+ * @param args The application command-line arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ int exitCode = mainWithExitCode(args);
+ System.exit(exitCode);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(100);
+ }
+ }
+
+ /**
+ * Launch the launcher.
+ *
+ * @param args The application command-line arguments.
+ * @return an integer exit code
+ * @throws Exception If an error occurs.
+ */
+ public static int mainWithExitCode(String[] args) throws Exception {
+ String classworldsConf = System.getProperty(CLASSWORLDS_CONF);
+ InputStream is;
+ Launcher launcher = new Launcher();
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ launcher.setSystemClassLoader(cl);
+ if (classworldsConf != null) {
+ is = Files.newInputStream(Paths.get(classworldsConf));
+ } else {
+ if ("true".equals(System.getProperty("classworlds.bootstrapped"))) {
+ is = cl.getResourceAsStream(UBERJAR_CONF_DIR + CLASSWORLDS_CONF);
+ } else {
+ is = cl.getResourceAsStream(CLASSWORLDS_CONF);
+ }
+ }
+ if (is == null) {
+ throw new Exception("classworlds configuration not specified nor found in the classpath");
+ }
+ launcher.configure(is);
+ is.close();
+ try {
+ launcher.launch(args);
+ } catch (InvocationTargetException e) {
+ ClassRealm realm = launcher.getWorld().getRealm(launcher.getMainRealmName());
+ URL[] constituents = realm.getURLs();
+ System.out.println("---------------------------------------------------");
+ for (int i = 0; i < constituents.length; i++) {
+ System.out.println("constituent[" + i + "]: " + constituents[i]);
+ }
+ System.out.println("---------------------------------------------------");
+ // Decode ITE (if we can)
+ Throwable t = e.getTargetException();
+ if (t instanceof Exception) {
+ throw (Exception) t;
+ }
+ if (t instanceof Error) {
+ throw (Error) t;
+ }
+ // Else just toss the ITE
+ throw e;
+ }
+ return launcher.getExitCode();
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java
new file mode 100644
index 0000000000..97833a35fc
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java
@@ -0,0 +1,450 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.strategy.Strategy;
+import org.codehaus.plexus.classworlds.strategy.StrategyFactory;
+
+/**
+ * The class loading gateway. Each class realm has access to a base class loader, imports form zero or more other class
+ * loaders, an optional parent class loader and of course its own class path. When queried for a class/resource, a class
+ * realm will always query its base class loader first before it delegates to a pluggable strategy. The strategy in turn
+ * controls the order in which imported class loaders, the parent class loader and the realm itself are searched. The
+ * base class loader is assumed to be capable of loading of the bootstrap classes.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ * @author Jason van Zyl
+ */
+public class ClassRealm extends URLClassLoader {
+
+ private final ClassWorld world;
+
+ private final String id;
+
+ private SortedSet<Entry> foreignImports;
+
+ private SortedSet<Entry> parentImports;
+
+ private Strategy strategy;
+
+ private ClassLoader parentClassLoader;
+
+ /**
+ * Creates a new class realm.
+ *
+ * @param world The class world this realm belongs to, must not be <code>null</code>.
+ * @param id The identifier for this realm, must not be <code>null</code>.
+ * @param baseClassLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
+ * loader.
+ */
+ public ClassRealm(ClassWorld world, String id, ClassLoader baseClassLoader) {
+ super(new URL[0], baseClassLoader);
+ this.world = world;
+ this.id = id;
+ foreignImports = new TreeSet<>();
+ strategy = StrategyFactory.getStrategy(this);
+ }
+
+ /**
+ * Creates a new class realm with a specific class loading strategy.
+ *
+ * @param world The class world this realm belongs to, must not be <code>null</code>.
+ * @param id The identifier for this realm, must not be <code>null</code>.
+ * @param baseClassLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
+ * loader.
+ * @param strategy The class loading strategy, must not be <code>null</code>.
+ */
+ public ClassRealm(ClassWorld world, String id, ClassLoader baseClassLoader, Strategy strategy) {
+ super(new URL[0], baseClassLoader);
+ this.world = world;
+ this.id = id;
+ foreignImports = new TreeSet<>();
+ this.strategy = Objects.requireNonNull(strategy, "strategy cannot be null");
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public ClassWorld getWorld() {
+ return this.world;
+ }
+
+ public void importFromParent(String packageName) {
+ if (parentImports == null) {
+ parentImports = new TreeSet<>();
+ }
+ parentImports.add(new Entry(null, packageName));
+ }
+
+ boolean isImportedFromParent(String name) {
+ if (parentImports != null && !parentImports.isEmpty()) {
+ for (Entry entry : parentImports) {
+ if (entry.matches(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public void importFrom(String realmId, String packageName) throws NoSuchRealmException {
+ importFrom(getWorld().getRealm(realmId), packageName);
+ }
+
+ public void importFrom(ClassLoader classLoader, String packageName) {
+ foreignImports.add(new Entry(classLoader, packageName));
+ }
+
+ public ClassLoader getImportClassLoader(String name) {
+ for (Entry entry : foreignImports) {
+ if (entry.matches(name)) {
+ return entry.getClassLoader();
+ }
+ }
+ return null;
+ }
+
+ public Collection<ClassRealm> getImportRealms() {
+ Collection<ClassRealm> importRealms = new HashSet<>();
+ for (Entry entry : foreignImports) {
+ if (entry.getClassLoader() instanceof ClassRealm) {
+ importRealms.add((ClassRealm) entry.getClassLoader());
+ }
+ }
+ return importRealms;
+ }
+
+ public Strategy getStrategy() {
+ return strategy;
+ }
+
+ /**
+ * Replaces the class loading strategy for this realm.
+ *
+ * @param strategy the new strategy, must not be {@code null}
+ */
+ public void setStrategy(Strategy strategy) {
+ this.strategy = Objects.requireNonNull(strategy, "strategy cannot be null");
+ }
+
+ public void setParentClassLoader(ClassLoader parentClassLoader) {
+ this.parentClassLoader = parentClassLoader;
+ }
+
+ public ClassLoader getParentClassLoader() {
+ return parentClassLoader;
+ }
+
+ public void setParentRealm(ClassRealm realm) {
+ this.parentClassLoader = realm;
+ }
+
+ public ClassRealm getParentRealm() {
+ return (parentClassLoader instanceof ClassRealm) ? (ClassRealm) parentClassLoader : null;
+ }
+
+ // Implementation of the original method signature for backward compatibility
+ public ClassRealm createChildRealm(String id) throws DuplicateRealmException {
+ ClassRealm childRealm = getWorld().newRealm(id, (ClassLoader) null);
+ childRealm.setParentRealm(this);
+ return childRealm;
+ }
+
+ public void addURL(URL url) {
+ String urlStr = url.toExternalForm();
+ if (urlStr.startsWith("jar:") && urlStr.endsWith("!/")) {
+ urlStr = urlStr.substring(4, urlStr.length() - 2);
+ try {
+ url = new URL(urlStr);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+ super.addURL(url);
+ }
+
+ public void addExports(String moduleName, String packageName) {
+ world.addExports(moduleName, packageName, getUnnamedModule());
+ }
+
+ public void addOpens(String moduleName, String packageName) {
+ world.addOpens(moduleName, packageName, getUnnamedModule());
+ }
+
+ public void addReads(String moduleName) {
+ world.addReads(moduleName, getUnnamedModule());
+ }
+
+ // ----------------------------------------------------------------------
+ // We delegate to the Strategy here so that we can change the behavior
+ // of any existing ClassRealm.
+ // ----------------------------------------------------------------------
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return loadClass(name, false);
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ return unsynchronizedLoadClass(name, resolve);
+ }
+
+ private Class<?> unsynchronizedLoadClass(String name, boolean resolve) throws ClassNotFoundException {
+ try {
+ // first, try loading bootstrap classes
+ return super.loadClass(name, resolve);
+ } catch (ClassNotFoundException e) {
+ // next, try loading via imports, self and parent as controlled by strategy
+ return strategy.loadClass(name);
+ }
+ }
+
+ // overwrites
+ // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)
+ // introduced in Java9
+ protected Class<?> findClass(String moduleName, String name) {
+ if (moduleName != null) {
+ return null;
+ }
+ try {
+ return findClassInternal(name);
+ } catch (ClassNotFoundException e) {
+ try {
+ return strategy.getRealm().findClass(name);
+ } catch (ClassNotFoundException nestedException) {
+ return null;
+ }
+ }
+ }
+
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ /*
+ * NOTE: This gets only called from ClassLoader.loadClass(Class, boolean) while we try to check for bootstrap
+ * stuff. Don't scan our class path yet, loadClassFromSelf() will do this later when called by the strategy.
+ */
+ throw new ClassNotFoundException(name);
+ }
+
+ protected Class<?> findClassInternal(String name) throws ClassNotFoundException {
+ return super.findClass(name);
+ }
+
+ public URL getResource(String name) {
+ URL resource = super.getResource(name);
+ return resource != null ? resource : strategy.getResource(name);
+ }
+
+ public URL findResource(String name) {
+ return super.findResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Collection<URL> resources = new LinkedHashSet<>(Collections.list(super.getResources(name)));
+ resources.addAll(Collections.list(strategy.getResources(name)));
+ return Collections.enumeration(resources);
+ }
+
+ public Enumeration<URL> findResources(String name) throws IOException {
+ return super.findResources(name);
+ }
+
+ // ----------------------------------------------------------------------------
+ // Display methods
+ // ----------------------------------------------------------------------------
+
+ public void display() {
+ display(System.out);
+ }
+
+ public void display(PrintStream out) {
+ out.println("-----------------------------------------------------");
+ for (ClassRealm cr = this; cr != null; cr = (ClassRealm) cr.getParentRealm()) {
+ out.println("realm = " + cr.getId());
+ out.println("strategy = " + cr.getStrategy().getClass().getName());
+ showUrls(cr, out);
+ out.println();
+ }
+ out.println("-----------------------------------------------------");
+ }
+
+ private static void showUrls(ClassRealm classRealm, PrintStream out) {
+ URL[] urls = classRealm.getURLs();
+ for (int i = 0; i < urls.length; i++) {
+ out.println("urls[" + i + "] = " + urls[i]);
+ }
+ out.println("Number of foreign imports: " + classRealm.foreignImports.size());
+ for (Entry entry : classRealm.foreignImports) {
+ out.println("import: " + entry);
+ }
+ if (classRealm.parentImports != null) {
+ out.println("Number of parent imports: " + classRealm.parentImports.size());
+ for (Entry entry : classRealm.parentImports) {
+ out.println("import: " + entry);
+ }
+ }
+ }
+
+ public String toString() {
+ return "ClassRealm[" + getId() + ", parent: " + getParentClassLoader() + "]";
+ }
+
+ // ---------------------------------------------------------------------------------------------
+ // Search methods that can be ordered by strategies to load a class
+ // ---------------------------------------------------------------------------------------------
+
+ public Class<?> loadClassFromImport(String name) {
+ ClassLoader importClassLoader = getImportClassLoader(name);
+ if (importClassLoader != null) {
+ try {
+ return importClassLoader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public Class<?> loadClassFromSelf(String name) {
+ synchronized (getClassLoadingLock(name)) {
+ try {
+ Class<?> clazz = findLoadedClass(name);
+ if (clazz == null) {
+ clazz = findClassInternal(name);
+ }
+ return clazz;
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ public Class<?> loadClassFromParent(String name) {
+ ClassLoader parent = getParentClassLoader();
+ if (parent != null && isImportedFromParent(name)) {
+ try {
+ return parent.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ // ---------------------------------------------------------------------------------------------
+ // Search methods that can be ordered by strategies to get a resource
+ // ---------------------------------------------------------------------------------------------
+
+ public URL loadResourceFromImport(String name) {
+ ClassLoader importClassLoader = getImportClassLoader(name);
+ if (importClassLoader != null) {
+ return importClassLoader.getResource(name);
+ }
+ return null;
+ }
+
+ public URL loadResourceFromSelf(String name) {
+ return findResource(name);
+ }
+
+ public URL loadResourceFromParent(String name) {
+ ClassLoader parent = getParentClassLoader();
+ if (parent != null && isImportedFromParent(name)) {
+ return parent.getResource(name);
+ } else {
+ return null;
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------
+ // Search methods that can be ordered by strategies to get resources
+ // ---------------------------------------------------------------------------------------------
+
+ public Enumeration<URL> loadResourcesFromImport(String name) {
+ ClassLoader importClassLoader = getImportClassLoader(name);
+ if (importClassLoader != null) {
+ try {
+ return importClassLoader.getResources(name);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public Enumeration<URL> loadResourcesFromSelf(String name) {
+ try {
+ return findResources(name);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public Enumeration<URL> loadResourcesFromParent(String name) {
+ ClassLoader parent = getParentClassLoader();
+ if (parent != null && isImportedFromParent(name)) {
+ try {
+ return parent.getResources(name);
+ } catch (IOException e) {
+ // eat it
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ }
+
+ static {
+ registerAsParallelCapable();
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/DuplicateRealmException.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/DuplicateRealmException.java
new file mode 100644
index 0000000000..7e2a34dcfc
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/DuplicateRealmException.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.ClassWorldException;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * Indicates an attempt to add a <code>ClassRealm</code> to a
+ * <code>ClassWorld</code> with a duplicate id.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class DuplicateRealmException extends ClassWorldException {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The realm id.
+ */
+ private String id;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param id The realm id.
+ */
+ public DuplicateRealmException(ClassWorld world, String id) {
+ super(world, "Duplicate realm: " + id);
+ this.id = id;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the duplicate realm id.
+ *
+ * @return The id.
+ */
+ public String getId() {
+ return this.id;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/Entry.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/Entry.java
new file mode 100644
index 0000000000..31798eca5c
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/Entry.java
@@ -0,0 +1,199 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * Import description entry.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+class Entry implements Comparable<Entry> {
+
+ final ClassLoader classLoader;
+
+ final String pkgName;
+
+ Entry(ClassLoader realm, String pkgName) {
+ this.classLoader = realm;
+ this.pkgName = pkgName;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the class loader.
+ *
+ * @return The class loader.
+ */
+ ClassLoader getClassLoader() {
+ return this.classLoader;
+ }
+
+ /**
+ * Retrieve the package name.
+ *
+ * @return The package name.
+ */
+ String getPackageName() {
+ return this.pkgName;
+ }
+
+ /**
+ * Determine if the class/resource name matches the package
+ * described by this entry.
+ *
+ * @param name The class or resource name to test, must not be <code>null</code>.
+ * @return <code>true</code> if this entry matches the
+ * classname, otherwise <code>false</code>.
+ */
+ boolean matches(String name) {
+ String pkg = getPackageName();
+ if (pkg.endsWith(".*")) {
+ String pkgName;
+ if (name.indexOf('/') < 0) {
+ // a binary class name, e.g. java.lang.Object
+ int index = name.lastIndexOf('.');
+ pkgName = (index < 0) ? "" : name.substring(0, index);
+ } else {
+ // a resource name, e.g. java/lang/Object.class
+ int index = name.lastIndexOf('/');
+ pkgName = (index < 0) ? "" : name.substring(0, index).replace('/', '.');
+ }
+ return pkgName.length() == pkg.length() - 2 && pkg.regionMatches(0, pkgName, 0, pkgName.length());
+ } else if (pkg.length() > 0) {
+ if (name.indexOf('/') < 0) {
+ // a binary class name, e.g. java.lang.Object
+ if (name.startsWith(pkg)) {
+ if (name.length() == pkg.length()) {
+ // exact match of class name
+ return true;
+ } else if (name.charAt(pkg.length()) == '.') {
+ // prefix match of package name
+ return true;
+ } else if (name.charAt(pkg.length()) == '$') {
+ // prefix match of enclosing type
+ return true;
+ }
+ }
+ } else {
+ // a resource name, e.g. java/lang/Object.class
+ if (name.equals(pkg)) {
+ // exact match of resource name
+ return true;
+ }
+ pkg = pkg.replace('.', '/');
+ if (name.startsWith(pkg) && name.length() > pkg.length()) {
+ if (name.charAt(pkg.length()) == '/') {
+ // prefix match of package directory
+ return true;
+ } else if (name.charAt(pkg.length()) == '$') {
+ // prefix match of nested class file
+ return true;
+ } else if (name.length() == pkg.length() + 6 && name.endsWith(".class")) {
+ // exact match of class file
+ return true;
+ }
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // java.lang.Comparable
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Compare this entry to another for relative ordering.
+ * <p/>
+ * <p/>
+ * The natural ordering of Entry objects is reverse-alphabetical
+ * based upon package name.
+ * </p>
+ *
+ * @param that The object to compare.
+ * @return -1 if this object sorts before that object, 0
+ * if they are equal, or 1 if this object sorts
+ * after that object.
+ */
+ public int compareTo(Entry that) {
+ // We are reverse sorting this list, so that
+ // we get longer matches first:
+ //
+ // com.werken.foo.bar
+ // com.werken.foo
+ // com.werken
+ return -(getPackageName().compareTo(that.getPackageName()));
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // java.lang.Object
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Test this entry for equality to another.
+ * <p/>
+ * <p/>
+ * Consistent with {@link #compareTo}, this method tests
+ * for equality purely on the package name.
+ * </p>
+ *
+ * @param thatObj The object to compare
+ * @return <code>true</code> if the two objects are
+ * semantically equivalent, otherwise <code>false</code>.
+ */
+ public boolean equals(Object thatObj) {
+ Entry that = (Entry) thatObj;
+ return getPackageName().equals(that.getPackageName());
+ }
+
+ /**
+ * <p/>
+ * Consistent with {@link #equals}, this method creates a hashCode
+ * based on the packagename.
+ * </p>
+ */
+ public int hashCode() {
+ return getPackageName().hashCode();
+ }
+
+ public String toString() {
+ return "Entry[import " + getPackageName() + " from realm " + getClassLoader() + "]";
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java
new file mode 100644
index 0000000000..dfb2eefa91
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java
@@ -0,0 +1,74 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.function.Predicate;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+
+/**
+ * Similar to {@link ClassRealm} but only exposing some resources of the underlying URL.
+ * Only supposed to be called from {@link ClassWorld}.
+ */
+public class FilteredClassRealm extends ClassRealm {
+ private final Predicate<String> filter;
+
+ /**
+ * Creates a new class realm.
+ *
+ * @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
+ * @param world The class world this realm belongs to, must not be <code>null</code>.
+ * @param id The identifier for this realm, must not be <code>null</code>.
+ * @param baseClassLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
+ * loader.
+ */
+ public FilteredClassRealm(Predicate<String> filter, ClassWorld world, String id, ClassLoader baseClassLoader) {
+ super(world, id, baseClassLoader);
+ this.filter = filter;
+ }
+
+ @Override
+ protected Class<?> findClassInternal(String name) throws ClassNotFoundException {
+ String resourceName = name.replace('.', '/').concat(".class");
+ if (!filter.test(resourceName)) {
+ throw new ClassNotFoundException(name);
+ }
+ return super.findClassInternal(name);
+ }
+
+ @Override
+ public URL findResource(String name) {
+ if (!filter.test(name)) {
+ return null;
+ }
+ return super.findResource(name);
+ }
+
+ @Override
+ public Enumeration<URL> findResources(String name) throws IOException {
+ if (!filter.test(name)) {
+ return Collections.emptyEnumeration();
+ }
+ return super.findResources(name);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/NoSuchRealmException.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/NoSuchRealmException.java
new file mode 100644
index 0000000000..7837d86461
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/NoSuchRealmException.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.ClassWorldException;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * Indicates an attempt to retrieve a <code>ClassRealm</code> from a
+ * <code>ClassWorld</code> with an invalid id.
+ *
+ * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
+ */
+public class NoSuchRealmException extends ClassWorldException {
+ // ------------------------------------------------------------
+ // Instance members
+ // ------------------------------------------------------------
+
+ /**
+ * The realm id.
+ */
+ private String id;
+
+ // ------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------
+
+ /**
+ * Construct.
+ *
+ * @param world The world.
+ * @param id The realm id.
+ */
+ public NoSuchRealmException(ClassWorld world, String id) {
+ super(world, "No such realm: " + id);
+ this.id = id;
+ }
+
+ // ------------------------------------------------------------
+ // Instance methods
+ // ------------------------------------------------------------
+
+ /**
+ * Retrieve the invalid realm id.
+ *
+ * @return The id.
+ */
+ public String getId() {
+ return this.id;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/AbstractStrategy.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/AbstractStrategy.java
new file mode 100644
index 0000000000..a8f0be1e17
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/AbstractStrategy.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+
+import org.codehaus.plexus.classworlds.UrlUtils;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+/**
+ * @author Jason van Zyl
+ */
+public abstract class AbstractStrategy implements Strategy {
+
+ protected ClassRealm realm;
+
+ public AbstractStrategy(ClassRealm realm) {
+ this.realm = realm;
+ }
+
+ protected String getNormalizedResource(String name) {
+ return UrlUtils.normalizeUrlPath(name);
+ }
+
+ protected Enumeration<URL> combineResources(Enumeration<URL> en1, Enumeration<URL> en2, Enumeration<URL> en3) {
+ Collection<URL> urls = new LinkedHashSet<>();
+ addAll(urls, en1);
+ addAll(urls, en2);
+ addAll(urls, en3);
+ return Collections.enumeration(urls);
+ }
+
+ private void addAll(Collection<URL> target, Enumeration<URL> en) {
+ if (en != null) {
+ while (en.hasMoreElements()) {
+ target.add(en.nextElement());
+ }
+ }
+ }
+
+ public ClassRealm getRealm() {
+ return realm;
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/OsgiBundleStrategy.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/OsgiBundleStrategy.java
new file mode 100644
index 0000000000..41867c7c0f
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/OsgiBundleStrategy.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2010 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+public class OsgiBundleStrategy extends AbstractStrategy {
+
+ // java.* from parent
+ // imported packages [Import-Package header with explicit constraints on the exporter]
+ // requires bundle [Required-Bundle]
+ // self [Bundle-Classpath header]
+ // attached fragments
+ //
+ // We need to trya and be OSGi r4 compliant in the loading of all the bundles so that we can try to
+ // load eclipse without requiring equinox. Or any other OSGi container for that matter.
+ public OsgiBundleStrategy(ClassRealm realm) {
+ super(realm);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ Class<?> clazz = realm.loadClassFromImport(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromSelf(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromParent(name);
+ if (clazz == null) {
+ throw new ClassNotFoundException(name);
+ }
+ }
+ }
+ return clazz;
+ }
+
+ public URL getResource(String name) {
+ URL resource = realm.loadResourceFromImport(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromSelf(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromParent(name);
+ }
+ }
+ return resource;
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Enumeration<URL> imports = realm.loadResourcesFromImport(name);
+ Enumeration<URL> self = realm.loadResourcesFromSelf(name);
+ Enumeration<URL> parent = realm.loadResourcesFromParent(name);
+ return combineResources(imports, self, parent);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/ParentFirstStrategy.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/ParentFirstStrategy.java
new file mode 100644
index 0000000000..12243b25a3
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/ParentFirstStrategy.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+/**
+ * @author Jason van Zyl
+ */
+public class ParentFirstStrategy extends AbstractStrategy {
+
+ public ParentFirstStrategy(ClassRealm realm) {
+ super(realm);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ Class<?> clazz = realm.loadClassFromImport(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromParent(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromSelf(name);
+ if (clazz == null) {
+ throw new ClassNotFoundException(name);
+ }
+ }
+ }
+ return clazz;
+ }
+
+ public URL getResource(String name) {
+ URL resource = realm.loadResourceFromImport(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromParent(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromSelf(name);
+ }
+ }
+ return resource;
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Enumeration<URL> imports = realm.loadResourcesFromImport(name);
+ Enumeration<URL> parent = realm.loadResourcesFromParent(name);
+ Enumeration<URL> self = realm.loadResourcesFromSelf(name);
+ return combineResources(imports, parent, self);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/SelfFirstStrategy.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/SelfFirstStrategy.java
new file mode 100644
index 0000000000..34116474a6
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/SelfFirstStrategy.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+/**
+ * @author Jason van Zyl
+ */
+public class SelfFirstStrategy extends AbstractStrategy {
+
+ public SelfFirstStrategy(ClassRealm realm) {
+ super(realm);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ Class<?> clazz = realm.loadClassFromImport(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromSelf(name);
+ if (clazz == null) {
+ clazz = realm.loadClassFromParent(name);
+ if (clazz == null) {
+ throw new ClassNotFoundException(name);
+ }
+ }
+ }
+ return clazz;
+ }
+
+ public URL getResource(String name) {
+ URL resource = realm.loadResourceFromImport(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromSelf(name);
+ if (resource == null) {
+ resource = realm.loadResourceFromParent(name);
+ }
+ }
+ return resource;
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Enumeration<URL> imports = realm.loadResourcesFromImport(name);
+ Enumeration<URL> self = realm.loadResourcesFromSelf(name);
+ Enumeration<URL> parent = realm.loadResourcesFromParent(name);
+ return combineResources(imports, self, parent);
+ }
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/Strategy.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/Strategy.java
new file mode 100644
index 0000000000..ad382a5392
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/Strategy.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+/**
+ * A strategy is a class for defining how classes and resources are located
+ * in classworlds.
+ */
+public interface Strategy {
+
+ Class<?> loadClass(String name) throws ClassNotFoundException;
+
+ URL getResource(String name);
+
+ Enumeration<URL> getResources(String name) throws IOException;
+
+ ClassRealm getRealm();
+}
diff --git a/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/StrategyFactory.java b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/StrategyFactory.java
new file mode 100644
index 0000000000..a9afe599e7
--- /dev/null
+++ b/impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/StrategyFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+
+/**
+ * StrategyFactory loads a strategy, either default or from a given hint.
+ *
+ * <p>Supported hints:
+ * <ul>
+ * <li>{@code "self-first"} (default) — realm's own classpath is searched before the parent</li>
+ * <li>{@code "parent-first"} — parent classloader is searched before the realm's own classpath</li>
+ * <li>{@code "osgi"} — OSGi bundle loading order (java.*, imports, self, fragments)</li>
+ * </ul>
+ */
+public class StrategyFactory {
+
+ public static Strategy getStrategy(ClassRealm realm) {
+ return getStrategy(realm, "self-first");
+ }
+
+ public static Strategy getStrategy(ClassRealm realm, String hint) {
+ if (hint == null || hint.isEmpty() || "self-first".equals(hint) || "default".equals(hint)) {
+ return new SelfFirstStrategy(realm);
+ } else if ("parent-first".equals(hint)) {
+ return new ParentFirstStrategy(realm);
+ } else if ("osgi".equals(hint)) {
+ return new OsgiBundleStrategy(realm);
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown strategy hint '" + hint + "': supported hints are 'self-first', 'parent-first', 'osgi'");
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/AbstractClassWorldsTestCase.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/AbstractClassWorldsTestCase.java
new file mode 100644
index 0000000000..846be0b1a1
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/AbstractClassWorldsTestCase.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.net.URL;
+
+import org.junit.jupiter.api.BeforeAll;
+
+/**
+ * @author Jason van Zyl
+ */
+public abstract class AbstractClassWorldsTestCase {
+
+ /**
+ * Generate test fixture JARs once per test run into {@code target/test-jars/}.
+ * Using JUnit 5 {@code @BeforeAll} with a non-static method requires
+ * {@code @TestInstance(Lifecycle.PER_CLASS)}, but a static method works with default lifecycle.
+ * We keep it static so subclasses need no annotation.
+ */
+ @BeforeAll
+ static void generateTestJars() throws Exception {
+ TestJarGenerator generator = new TestJarGenerator(TestUtil.getTestJarsDir());
+ generator.generate();
+ }
+
+ protected URL getJarUrl(String jarName) {
+ return TestUtil.getTestResourceUrl(jarName);
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassView.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassView.java
new file mode 100644
index 0000000000..cffd6a1a1c
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassView.java
@@ -0,0 +1,130 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+public class ClassView {
+ /**
+ * * Formats Class information for debug output purposes.
+ * *
+ * * @param clz the Class to print information for
+ * *
+ * * @return a String describing the Class in detail
+ */
+ public static String toString(Class<?> clz) {
+ if (clz.isPrimitive()) {
+ return clz.toString();
+ } else if (clz.isArray()) {
+ return "Array of " + toString(clz.getComponentType());
+ } else if (clz.isInterface()) {
+ return toInterfaceString(clz, "");
+ } else {
+ return toClassString(clz, "");
+ }
+ }
+
+ /**
+ * * Formats Class information for debug output purposes.
+ * *
+ * * @param clz the Class to print information for
+ * * @param sIndent the indentation to precede each line of output
+ * *
+ * * @return a String describing the Class in detail
+ */
+ private static String toClassString(Class<?> clz, String sIndent) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(sIndent)
+ .append("Class ")
+ .append(clz.getName())
+ .append(" (")
+ .append(toString(clz.getClassLoader()))
+ .append(')');
+
+ sIndent += " ";
+
+ Class<?>[] aclz = clz.getInterfaces();
+ for (Class<?> aClass : aclz) {
+ sb.append('\n').append(toInterfaceString(aClass, sIndent));
+ }
+
+ clz = clz.getSuperclass();
+ if (clz != null) {
+ sb.append('\n').append(toClassString(clz, sIndent));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * * Formats interface information for debug output purposes.
+ * *
+ * * @param clz the interface Class to print information for
+ * * @param sIndent the indentation to precede each line of output
+ * *
+ * * @return a String describing the interface Class in detail
+ */
+ private static String toInterfaceString(Class<?> clz, String sIndent) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(sIndent)
+ .append("Interface ")
+ .append(clz.getName())
+ .append(" (")
+ .append(toString(clz.getClassLoader()))
+ .append(')');
+
+ Class<?>[] aclz = clz.getInterfaces();
+ for (Class<?> aClass : aclz) {
+ clz = aClass;
+
+ sb.append('\n').append(toInterfaceString(clz, sIndent + " "));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * * Format a description for the specified ClassLoader object.
+ * *
+ * * @param loader the ClassLoader instance (or null)
+ * *
+ * * @return a String description of the ClassLoader
+ */
+ private static String toString(ClassLoader loader) {
+ if (loader == null) {
+ return "System ClassLoader";
+ }
+
+ return "ClassLoader class=" + loader.getClass().getName() + ", hashCode=" + loader.hashCode();
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
new file mode 100644
index 0000000000..75f6ebd399
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ClassWorldTest extends AbstractClassWorldsTestCase {
+ private ClassWorld world;
+
+ @BeforeEach
+ public void setUp() {
+ this.world = new ClassWorld();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.world = null;
+ }
+
+ @Test
+ void testEmpty() {
+ assertTrue(this.world.getRealms().isEmpty());
+ }
+
+ @Test
+ void testNewRealm() throws Exception {
+ ClassRealm realm = this.world.newRealm("foo");
+
+ assertNotNull(realm);
+ }
+
+ @Test
+ void testGetRealm() throws Exception {
+ ClassRealm realm = this.world.newRealm("foo");
+
+ assertSame(realm, this.world.getRealm("foo"));
+ }
+
+ @Test
+ void testNewRealmDuplicate() {
+ try {
+ this.world.newRealm("foo");
+ this.world.newRealm("foo");
+
+ fail("throw DuplicateRealmException");
+ } catch (DuplicateRealmException e) {
+ // expected and correct
+
+ assertSame(this.world, e.getWorld());
+
+ assertEquals("foo", e.getId());
+ }
+ }
+
+ @Test
+ void testGetRealmNoSuch() {
+ try {
+ this.world.getRealm("foo");
+ fail("throw NoSuchRealmException");
+ } catch (NoSuchRealmException e) {
+ // expected and correct
+
+ assertSame(this.world, e.getWorld());
+
+ assertEquals("foo", e.getId());
+ }
+ }
+
+ @Test
+ void testGetRealms() throws Exception {
+ assertTrue(this.world.getRealms().isEmpty());
+
+ ClassRealm foo = this.world.newRealm("foo");
+
+ assertEquals(1, this.world.getRealms().size());
+
+ assertTrue(this.world.getRealms().contains(foo));
+
+ ClassRealm bar = this.world.newRealm("bar");
+
+ assertEquals(2, this.world.getRealms().size());
+
+ assertTrue(this.world.getRealms().contains(bar));
+ }
+
+ @Test
+ void testPLX334() throws Exception {
+ ClassLoader loader = new URLClassLoader(new URL[] {getJarUrl("component1-1.0.jar")});
+ world.newRealm("netbeans", loader);
+ ClassRealm plexus = world.newRealm("plexus");
+ plexus.importFrom("netbeans", "META-INF/plexus");
+ plexus.importFrom("netbeans", "org.codehaus.plexus");
+ Enumeration<URL> e = plexus.getResources("META-INF/plexus/components.xml");
+ assertNotNull(e);
+ int resourceCount = 0;
+ for (Enumeration<URL> resources = e; resources.hasMoreElements(); ) {
+ URL obj = resources.nextElement();
+ assertTrue(obj.getPath().contains("/component1-1.0.jar!/META-INF/plexus/components.xml"));
+ resourceCount++;
+ }
+ // assertEquals( 2, resourceCount );
+ // for some reason surefire-plugin 2.3 returned 2 items there:
+ // for example:
+ // jar:file:/home/mkleint/.m2/repository/org/codehaus/plexus/plexus-archiver/1.0-alpha-7/plexus-archiver-1.0-alpha-7.jar!/META-INF/plexus/components.xml
+ // jar:file:/home/mkleint/src/plexus-trunk/plexus-classworlds/src/test-jars/component1-1.0.jar!/META-INF/plexus/components.xml
+ // However only 1 is correct, which is actually returned by the 2.4 surefire-plugin
+
+ assertEquals(1, resourceCount);
+ Class<?> c = plexus.loadClass("org.codehaus.plexus.Component1");
+ assertNotNull(c);
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestJarGenerator.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestJarGenerator.java
new file mode 100644
index 0000000000..f316f446af
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestJarGenerator.java
@@ -0,0 +1,554 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+import javax.tools.DiagnosticCollector;
+import javax.tools.FileObject;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+/**
+ * Generates test fixture JARs at test time using the JDK's {@code javac} and {@code jar} tools,
+ * eliminating the need to commit pre-compiled binary JARs to version control.
+ *
+ * <p>All JARs are written to {@code target/test-jars/} under the module base directory.
+ * The generator is idempotent: existing JARs are overwritten on each run. Note that
+ * JARs from a previous run whose names are no longer generated are not deleted.
+ *
+ * <p>Called once per test run from {@link AbstractClassWorldsTestCase#generateTestJars()}.
+ */
+public class TestJarGenerator {
+
+ private final Path outputDir;
+ private final JavaCompiler compiler;
+
+ /** Map of compiled class name → bytecode, accumulated across compile() calls. */
+ private final Map<String, byte[]> compiledClasses = new LinkedHashMap<>();
+
+ public TestJarGenerator(Path outputDir) throws IOException {
+ this.outputDir = outputDir;
+ Files.createDirectories(outputDir);
+ this.compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ throw new IllegalStateException("No system Java compiler available – tests must run on a JDK, not a JRE.");
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Public entry point
+ // -------------------------------------------------------------------------
+
+ /** Generate all test fixture JARs into {@link #outputDir}. */
+ public void generate() throws Exception {
+ generateSimpleJars();
+ generateComponentJars();
+ generateMiscJars();
+ }
+
+ /** Simple single-class and deadlock fixture JARs. */
+ private void generateSimpleJars() throws Exception {
+ // Simple stubs – no external dependencies
+ compileClasses(
+ null,
+ src("a/A.java", "package a; public class A { public static void main(String[] a) {} }"),
+ src("a/Aa.java", "package a; public class Aa { public static int main(String[] a) { return 15; } }"));
+ jar(
+ "a.jar",
+ res("a.properties", "a properties\n"),
+ res("common.properties", "common.properties"),
+ cls("a/A.class"),
+ cls("a/Aa.class"));
+ compiledClasses.clear();
+
+ compileClasses(
+ null,
+ src(
+ "b/B.java",
+ "package b;\npublic class B {\n"
+ + " public static void main(String[] a, org.codehaus.plexus.classworlds.ClassWorld w) {}\n"
+ + "}"),
+ src(
+ "b/Bb.java",
+ "package b;\npublic class Bb {\n"
+ + " public static int main(String[] a, org.codehaus.plexus.classworlds.ClassWorld w) { return 45; }\n"
+ + "}"));
+ jar(
+ "b.jar",
+ res("b.properties", "name = b.properties"),
+ res("common.properties", "name = common.properties"),
+ cls("b/B.class"),
+ cls("b/Bb.class"));
+ compiledClasses.clear();
+
+ // b_old uses the legacy org.codehaus.classworlds.ClassWorld adapter
+ compileClasses(
+ null,
+ src(
+ "b/B.java",
+ "package b;\npublic class B {\n"
+ + " public static void main(String[] a, org.codehaus.classworlds.ClassWorld w) {}\n"
+ + "}"),
+ src(
+ "b/Bb.java",
+ "package b;\npublic class Bb {\n"
+ + " public static int main(String[] a, org.codehaus.classworlds.ClassWorld w) { return 0; }\n"
+ + "}"));
+ jar("b_old.jar", cls("b/B.class"), cls("b/Bb.class"));
+ compiledClasses.clear();
+
+ compileClasses(null, src("c/C.java", "package c; public class C {}"));
+ jar("c.jar", cls("c/C.class"));
+ compiledClasses.clear();
+
+ compileClasses(null, src("d/D.java", "package d; public class D {}"));
+ jar("d.jar", cls("d/D.class"));
+ compiledClasses.clear();
+
+ // deadlock: cross-extends to trigger classloader deadlock test
+ compileClasses(
+ null,
+ src("deadlock/B.java", "package deadlock; public class B {}"),
+ src("deadlock/D.java", "package deadlock; public class D {}"),
+ src("deadlock/A.java", "package deadlock; public class A extends B {}"),
+ src("deadlock/C.java", "package deadlock; public class C extends D {}"));
+ jar(
+ "deadlock.jar",
+ cls("deadlock/A.class"),
+ cls("deadlock/B.class"),
+ cls("deadlock/C.class"),
+ cls("deadlock/D.class"));
+ compiledClasses.clear();
+ }
+
+ /** Component0–5 plexus fixture JARs. */
+ private void generateComponentJars() throws Exception {
+ // circular: cross-reference between A and B, A has inner class C
+ compileClasses(
+ null,
+ src(
+ "A.java",
+ "public class A {\n"
+ + " public static B b = new B();\n"
+ + " public static C c = new C();\n"
+ + " public B foo(B arg) { return new B(); }\n"
+ + " public C foo(C arg) { return new C(); }\n"
+ + " public static A create(boolean arg) { return arg ? new A() : new C(); }\n"
+ + " public static class C extends A {}\n"
+ + "}"),
+ src(
+ "B.java",
+ "public class B {\n"
+ + " public A a;\n"
+ + " public A foo(A arg) { return new A(); }\n"
+ + "}"));
+ jar(
+ "circular-0.1.jar",
+ res("pom.xml", circularPom()),
+ res("src/main/java/org/A.java", circularSourceA()),
+ res("src/main/java/org/B.java", circularSourceB()),
+ res("META-INF/maven/test/circular/pom.xml", circularPom()),
+ res(
+ "META-INF/maven/test/circular/pom.properties",
+ "#Generated by Maven\nversion=0.1\ngroupId=test\nartifactId=circular\n"),
+ cls("A.class"),
+ cls("A$C.class"),
+ cls("B.class"));
+ compiledClasses.clear();
+
+ // component0..4: interface + impl pairs in org.codehaus.plexus package
+ for (int i = 0; i <= 4; i++) {
+ String iface = "Component" + i;
+ String impl = "Default" + iface;
+ compileClasses(
+ null,
+ src(
+ "org/codehaus/plexus/" + iface + ".java",
+ "package org.codehaus.plexus; public interface " + iface + " {}"),
+ src(
+ "org/codehaus/plexus/" + impl + ".java",
+ "package org.codehaus.plexus; public class " + impl + " implements " + iface + " {}"));
+ jar(
+ "component" + i + "-1.0.jar",
+ res(
+ "META-INF/plexus/components.xml",
+ plexusComponents("org.codehaus.plexus." + iface, "org.codehaus.plexus." + impl)),
+ cls("org/codehaus/plexus/" + iface + ".class"),
+ cls("org/codehaus/plexus/" + impl + ".class"));
+ compiledClasses.clear();
+ }
+
+ // component5: two versions with different method signatures (realm isolation test)
+ compileClasses(
+ null,
+ src("test/Component5.java", "package test; public interface Component5 { String printOld(); }"),
+ src(
+ "test/DefaultComponent5.java",
+ "package test; public class DefaultComponent5 implements Component5 {"
+ + " public String printOld() { return \"old\"; } }"));
+ jar(
+ "component5-1.0.jar",
+ res("META-INF/plexus/components.xml", plexusComponents("test.Component5", "test.DefaultComponent5")),
+ cls("test/Component5.class"),
+ cls("test/DefaultComponent5.class"));
+ compiledClasses.clear();
+
+ compileClasses(
+ null,
+ src("test/Component5.java", "package test; public interface Component5 { String printNew(); }"),
+ src(
+ "test/DefaultComponent5.java",
+ "package test; public class DefaultComponent5 implements Component5 {"
+ + " public String printNew() { return \"new\"; } }"));
+ jar(
+ "component5-2.0.jar",
+ res("META-INF/plexus/components.xml", plexusComponents("test.Component5", "test.DefaultComponent5")),
+ cls("test/Component5.class"),
+ cls("test/DefaultComponent5.class"));
+ compiledClasses.clear();
+ }
+
+ /** from-from, nested, and werkflow JARs. */
+ private void generateMiscJars() throws Exception {
+ // from-from: simple main class + META-INF/maven metadata
+ compileClasses(
+ null,
+ src(
+ "com/from/from/from/Main.java",
+ "package com.from.from.from; public class Main { public static void main(String[] a) {} }"));
+ jar(
+ "from-from-0.0.1-from-load-import.jar",
+ res("META-INF/maven/com.from/from-from/pom.xml", fromFromPom()),
+ res(
+ "META-INF/maven/com.from/from-from/pom.properties",
+ "artifactId=from-from\ngroupId=com.from\nversion=0.0.1-from-load-import"),
+ cls("com/from/from/from/Main.class"));
+ compiledClasses.clear();
+
+ // a.jar must exist before nested.jar (which embeds it)
+ buildNestedJar();
+
+ // werkflow: resource-only jar (no classes)
+ buildWerkflowJar();
+ }
+
+ // -------------------------------------------------------------------------
+ // Compilation helpers
+ // -------------------------------------------------------------------------
+
+ /**
+ * Compile one or more in-memory Java source units.
+ * Results are accumulated into {@link #compiledClasses}.
+ *
+ * @param extraClasspath additional JAR paths to add to the compile classpath (may be null)
+ */
+ private void compileClasses(List<Path> extraClasspath, SourceUnit... sources) throws Exception {
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+
+ try (StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, StandardCharsets.UTF_8)) {
+
+ // Build classpath: current test classpath + any extras
+ List<File> classpathFiles = currentClasspath();
+ if (extraClasspath != null) {
+ for (Path p : extraClasspath) {
+ classpathFiles.add(p.toFile());
+ }
+ }
+ fm.setLocation(StandardLocation.CLASS_PATH, classpathFiles);
+
+ // In-memory output file manager
+ InMemoryFileManager memFm = new InMemoryFileManager(fm, compiledClasses);
+
+ List<JavaFileObject> units = new ArrayList<>();
+ for (SourceUnit su : sources) {
+ units.add(su);
+ }
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, memFm, diagnostics, null, null, units);
+ boolean ok = task.call();
+ if (!ok) {
+ StringBuilder sb = new StringBuilder("Compilation failed:\n");
+ for (var d : diagnostics.getDiagnostics()) {
+ sb.append(d).append('\n');
+ }
+ throw new RuntimeException(sb.toString());
+ }
+ }
+ }
+
+ /** Collect the current JVM classpath entries as File objects. */
+ private static List<File> currentClasspath() {
+ List<File> result = new ArrayList<>();
+ String cp = System.getProperty("java.class.path", "");
+ for (String entry : cp.split(File.pathSeparator)) {
+ if (!entry.isEmpty()) {
+ result.add(new File(entry));
+ }
+ }
+ return result;
+ }
+
+ // -------------------------------------------------------------------------
+ // JAR assembly helpers
+ // -------------------------------------------------------------------------
+
+ /**
+ * Assemble a JAR from a mix of {@link ClassEntry} (bytecode from compiledClasses)
+ * and {@link ResourceEntry} (inline text content).
+ */
+ private void jar(String name, JarContent... entries) throws IOException {
+ Path out = outputDir.resolve(name);
+ Manifest mf = new Manifest();
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(out), mf)) {
+ for (JarContent e : entries) {
+ jos.putNextEntry(new JarEntry(e.entryName()));
+ jos.write(e.bytes(compiledClasses));
+ jos.closeEntry();
+ }
+ }
+ }
+
+ /** Build nested.jar: a.properties + lib/a.jar (a.jar embedded). */
+ private void buildNestedJar() throws IOException {
+ Path aJar = outputDir.resolve("a.jar");
+ byte[] aJarBytes = Files.readAllBytes(aJar);
+
+ Path out = outputDir.resolve("nested.jar");
+ Manifest mf = new Manifest();
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(out), mf)) {
+ jos.putNextEntry(new JarEntry("nested.properties"));
+ jos.write("nested.properties\n".getBytes(StandardCharsets.UTF_8));
+ jos.closeEntry();
+
+ jos.putNextEntry(new JarEntry("lib/a.jar"));
+ jos.write(aJarBytes);
+ jos.closeEntry();
+ }
+ }
+
+ /** Build werkflow.jar: resource-only (XML files, no classes). */
+ private void buildWerkflowJar() throws IOException {
+ Path out = outputDir.resolve("resources/werkflow.jar");
+ Files.createDirectories(out.getParent());
+
+ Manifest mf = new Manifest();
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(out), mf)) {
+ addXmlEntry(jos, "org/codehaus/werkflow/personality/basic/basic-syntax.xml", "<syntax/>");
+ addXmlEntry(jos, "org/codehaus/werkflow/personality/bpel4ws/bpel4ws-syntax.xml", "<syntax/>");
+ addXmlEntry(jos, "org/codehaus/werkflow/personality/common/core-idioms.xml", "<idioms/>");
+ }
+ }
+
+ private static void addXmlEntry(JarOutputStream jos, String name, String content) throws IOException {
+ jos.putNextEntry(new JarEntry(name));
+ jos.write(content.getBytes(StandardCharsets.UTF_8));
+ jos.closeEntry();
+ }
+
+ // -------------------------------------------------------------------------
+ // Static resource strings
+ // -------------------------------------------------------------------------
+
+ private static String circularPom() {
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ + " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0"
+ + " http://maven.apache.org/maven-v4_0_0.xsd\">\n"
+ + " <modelVersion>4.0.0</modelVersion>\n"
+ + " <groupId>test</groupId>\n"
+ + " <artifactId>circular</artifactId>\n"
+ + " <version>0.1</version>\n"
+ + "</project>\n";
+ }
+
+ private static String circularSourceA() {
+ return "public class A {\n"
+ + " public static B b = new B();\n"
+ + " public static C c = new C();\n"
+ + " public B foo( B arg ) { return new B(); }\n"
+ + " public C foo( C arg ) { return new C(); }\n"
+ + " public static A create( boolean arg) { return arg ? new A() : new C(); }\n"
+ + " public static class C extends A {}\n"
+ + "}\n";
+ }
+
+ private static String circularSourceB() {
+ return "public class B {\n" + " public A a;\n" + " public A foo( A arg ) { return new A(); }\n" + "}\n";
+ }
+
+ private static String plexusComponents(String role, String impl) {
+ return "<component-set>\n"
+ + " <components>\n"
+ + " <component>\n"
+ + " <role>" + role + "</role>\n"
+ + " <implementation>" + impl + "</implementation>\n"
+ + " </component>\n"
+ + " </components>\n"
+ + "</component-set>\n";
+ }
+
+ private static String fromFromPom() {
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ + " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0"
+ + " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ + " <modelVersion>4.0.0</modelVersion>\n"
+ + " <groupId>com.from</groupId>\n"
+ + " <artifactId>from-from</artifactId>\n"
+ + " <version>0.0.1-from-load-import</version>\n"
+ + "</project>\n";
+ }
+
+ // -------------------------------------------------------------------------
+ // DSL helpers for jar() entries
+ // -------------------------------------------------------------------------
+
+ interface JarContent {
+ String entryName();
+
+ byte[] bytes(Map<String, byte[]> compiled) throws IOException;
+ }
+
+ /** Bytecode entry — key is the slash-separated class name + ".class". */
+ record ClassEntry(String name) implements JarContent {
+ @Override
+ public String entryName() {
+ return name;
+ }
+
+ @Override
+ public byte[] bytes(Map<String, byte[]> compiled) {
+ // Strip ".class" to get the map key used by InMemoryFileManager
+ String key = name.endsWith(".class") ? name.substring(0, name.length() - 6) : name;
+ byte[] b = compiled.get(key);
+ if (b == null) {
+ throw new RuntimeException(
+ "No compiled bytecode for: " + key + " (available: " + compiled.keySet() + ")");
+ }
+ return b;
+ }
+ }
+
+ /** Inline text resource entry. */
+ record ResourceEntry(String name, String content) implements JarContent {
+ @Override
+ public String entryName() {
+ return name;
+ }
+
+ @Override
+ public byte[] bytes(Map<String, byte[]> compiled) {
+ return content.getBytes(StandardCharsets.UTF_8);
+ }
+ }
+
+ private static ClassEntry cls(String name) {
+ return new ClassEntry(name);
+ }
+
+ private static ResourceEntry res(String name, String content) {
+ return new ResourceEntry(name, content);
+ }
+
+ // -------------------------------------------------------------------------
+ // In-memory source unit
+ // -------------------------------------------------------------------------
+
+ static class SourceUnit extends SimpleJavaFileObject {
+ private final String src;
+
+ SourceUnit(String className, String src) {
+ super(URI.create("mem:///" + className.replace('.', '/') + ".java"), Kind.SOURCE);
+ this.src = src;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return src;
+ }
+ }
+
+ private static SourceUnit src(String path, String src) {
+ // path like "a/A.java" → class name "a.A"
+ String className = path.replace('/', '.').replace(".java", "");
+ return new SourceUnit(className, src);
+ }
+
+ // -------------------------------------------------------------------------
+ // In-memory output file manager
+ // -------------------------------------------------------------------------
+
+ /**
+ * Routes compiled {@code .class} output into a {@code Map<String, byte[]>}
+ * instead of writing to disk.
+ */
+ static class InMemoryFileManager extends javax.tools.ForwardingJavaFileManager<StandardJavaFileManager> {
+ private final Map<String, byte[]> classBytes;
+
+ InMemoryFileManager(StandardJavaFileManager delegate, Map<String, byte[]> classBytes) {
+ super(delegate);
+ this.classBytes = classBytes;
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(
+ Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
+ return new SimpleJavaFileObject(URI.create("mem:///" + className.replace('.', '/') + ".class"), kind) {
+ @Override
+ public OutputStream openOutputStream() {
+ return new ByteArrayOutputStream() {
+ @Override
+ public void close() throws IOException {
+ super.close();
+ // key uses '/' separator (matches JarEntry names without ".class")
+ classBytes.put(className.replace('.', '/'), toByteArray());
+ }
+ };
+ }
+ };
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestUtil.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestUtil.java
new file mode 100644
index 0000000000..f5423ec1ce
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestUtil.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * @author <a href="bwalding@jakarta.org">Ben Walding</a>
+ */
+public class TestUtil {
+
+ /**
+ * Returns the URL for a test fixture JAR (or resource file) by name.
+ *
+ * <p>JARs are looked up in {@code target/test-jars/} where {@link TestJarGenerator} writes them.
+ * The {@code resources/} sub-directory (e.g. {@code werkflow.jar}) lives under
+ * {@code target/test-jars/resources/}.
+ */
+ public static URL getTestResourceUrl(String resourceName) {
+ File resourceFile = getTestJarsDir().resolve(resourceName).toFile();
+ try {
+ return resourceFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Directory where generated test JARs are written ({@code <basedir>/target/test-jars/}). */
+ public static Path getTestJarsDir() {
+ return Paths.get(getBasedir(), "target", "test-jars");
+ }
+
+ public static String getBasedir() {
+ String basedir = System.getProperty("basedir");
+
+ /* do our best if we are not running from surefire */
+ if (basedir == null || basedir.length() <= 0) {
+ basedir = (new File("")).getAbsolutePath();
+ }
+
+ return basedir;
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/UrlUtilsTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/UrlUtilsTest.java
new file mode 100644
index 0000000000..cdecf0a44b
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/UrlUtilsTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class UrlUtilsTest {
+
+ @Test
+ public void testNormalizeUrlPath() {
+ assertEquals("org/codehaus/Test.class", UrlUtils.normalizeUrlPath("org/codehaus/Test.class"));
+ assertEquals("org/Test.class", UrlUtils.normalizeUrlPath("org/codehaus/../Test.class"));
+ assertEquals("../../some.jar/org/Test.class", UrlUtils.normalizeUrlPath("../../some.jar/org/Test.class"));
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParserTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParserTest.java
new file mode 100644
index 0000000000..838fc55811
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParserTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ConfigurationParserTest extends AbstractClassWorldsTestCase {
+
+ ConfigurationParser configurator = new ConfigurationParser(null, System.getProperties());
+
+ @Test
+ void testFilterUnterminated() {
+ try {
+ this.configurator.filter("${cheese");
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("Unterminated"));
+ }
+ }
+
+ @Test
+ void testFilterSolitary() throws Exception {
+ System.setProperty("classworlds.test.prop", "test prop value");
+
+ String result = this.configurator.filter("${classworlds.test.prop}");
+
+ assertEquals("test prop value", result);
+ }
+
+ @Test
+ void testFilterAtStart() throws Exception {
+ System.setProperty("classworlds.test.prop", "test prop value");
+
+ String result = this.configurator.filter("${classworlds.test.prop}cheese");
+
+ assertEquals("test prop valuecheese", result);
+ }
+
+ @Test
+ void testFilterAtEnd() throws Exception {
+ System.setProperty("classworlds.test.prop", "test prop value");
+
+ String result = this.configurator.filter("cheese${classworlds.test.prop}");
+
+ assertEquals("cheesetest prop value", result);
+ }
+
+ @Test
+ void testFilterMultiple() throws Exception {
+ System.setProperty("classworlds.test.prop.one", "test prop value one");
+
+ System.setProperty("classworlds.test.prop.two", "test prop value two");
+
+ String result =
+ this.configurator.filter("I like ${classworlds.test.prop.one} and ${classworlds.test.prop.two} a lot");
+
+ assertEquals("I like test prop value one and test prop value two a lot", result);
+ }
+
+ @Test
+ void testFilterNonExistent() {
+ try {
+ this.configurator.filter("${gollygeewillikers}");
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("No such property"));
+ }
+ }
+
+ @Test
+ void testFilterInMiddle() throws Exception {
+ System.setProperty("classworlds.test.prop", "test prop value");
+
+ String result = this.configurator.filter("cheese${classworlds.test.prop}toast");
+
+ assertEquals("cheesetest prop valuetoast", result);
+ }
+
+ @Test
+ void testFilterDefaultValueWhenAbsent() throws Exception {
+ // Property does not exist — should use the default after :-
+ System.clearProperty("classworlds.test.missing");
+
+ String result = this.configurator.filter("${classworlds.test.missing:-fallback}");
+
+ assertEquals("fallback", result);
+ }
+
+ @Test
+ void testFilterDefaultValueNotUsedWhenPresent() throws Exception {
+ System.setProperty("classworlds.test.present", "actual");
+
+ String result = this.configurator.filter("${classworlds.test.present:-fallback}");
+
+ assertEquals("actual", result);
+ }
+
+ @Test
+ void testFilterAlternateValueWhenPresent() throws Exception {
+ System.setProperty("classworlds.test.present", "actual");
+
+ String result = this.configurator.filter("${classworlds.test.present:+alternate}");
+
+ assertEquals("alternate", result);
+ }
+
+ @Test
+ void testFilterAlternateValueNotUsedWhenAbsent() {
+ System.clearProperty("classworlds.test.missing");
+
+ // ${missing:+alternate}: property absent → alternate is NOT used → treated as unresolved → exception
+ try {
+ this.configurator.filter("${classworlds.test.missing:+alternate}");
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ assertTrue(e.getMessage().startsWith("No such property"));
+ }
+ }
+
+ @Test
+ void testFilterDefaultValueWithSurroundingText() throws Exception {
+ System.clearProperty("classworlds.test.missing");
+
+ String result = this.configurator.filter("prefix/${classworlds.test.missing:-default}/suffix");
+
+ assertEquals("prefix/default/suffix", result);
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfiguratorTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfiguratorTest.java
new file mode 100644
index 0000000000..d8a3f73937
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfiguratorTest.java
@@ -0,0 +1,360 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Collection;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.TestUtil;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ConfiguratorTest extends AbstractClassWorldsTestCase {
+ private Launcher launcher;
+ private Configurator configurator;
+
+ @BeforeEach
+ public void setUp() {
+ this.launcher = new Launcher();
+ this.configurator = new Configurator(this.launcher);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.launcher = null;
+ this.configurator = null;
+ System.getProperties().remove("set.using.existent");
+ System.getProperties().remove("set.using.default");
+ System.getProperties().remove("set.using.nonexistent");
+ System.getProperties().remove("set.using.nonexistent.default");
+ System.getProperties().remove("set.using.missing");
+ System.getProperties().remove("set.using.filtered.default");
+ }
+
+ @Test
+ void testConfigureNonexistent() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("notfound.conf"));
+ fail("throw FileNotFoundException");
+ } catch (FileNotFoundException e) {
+ // expected and correct
+ }
+ }
+
+ @Test
+ void testConfigureDuplicateMain() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("dupe-main.conf"));
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("Duplicate main"));
+ }
+ }
+
+ @Test
+ void testConfigureDuplicateRealm() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("dupe-realm.conf"));
+ fail("throw DuplicateRealmException");
+ } catch (DuplicateRealmException e) {
+ // expected and correct
+ assertEquals("dupe.realm", e.getId());
+ }
+ }
+
+ @Test
+ void testConfigureEarlyImport() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("early-import.conf"));
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("Unhandled import"));
+ }
+ }
+
+ @Test
+ void testConfigureRealmSyntax() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("realm-syntax.conf"));
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("Invalid realm"));
+ }
+ }
+
+ @Test
+ void testConfigureValid() throws Exception {
+ this.configurator.configure(getConfigPath("valid.conf"));
+
+ assertEquals("org.apache.maven.app.App", this.launcher.getMainClassName());
+
+ assertEquals("maven", this.launcher.getMainRealmName());
+
+ ClassWorld world = this.launcher.getWorld();
+
+ Collection<ClassRealm> realms = world.getRealms();
+
+ assertEquals(4, realms.size());
+
+ assertNotNull(world.getRealm("ant"));
+ assertNotNull(world.getRealm("maven"));
+ assertNotNull(world.getRealm("xml"));
+
+ ClassRealm antRealm = world.getRealm("ant");
+ ClassRealm mavenRealm = world.getRealm("maven");
+ ClassRealm xmlRealm = world.getRealm("xml");
+ ClassRealm globRealm = world.getRealm("glob");
+
+ assertSame(null, antRealm.getImportClassLoader("org.apache.tools.Ant"));
+
+ // Ant has dependency to xerces:xercesImpl (test)
+ assertSame(null, antRealm.getImportClassLoader("org.xml.sax.SAXException"));
+
+ assertSame(xmlRealm, antRealm.getImportClassLoader("jakarta.xml.bind.JAXBException"));
+
+ assertSame(null, mavenRealm.getImportClassLoader("org.apache.maven.app.App"));
+
+ assertSame(xmlRealm, mavenRealm.getImportClassLoader("jakarta.xml.bind.JAXBException"));
+
+ URL[] urls = globRealm.getURLs();
+
+ assertArrayContains(urls, TestUtil.getTestResourceUrl("nested.jar"));
+ assertArrayContains(urls, TestUtil.getTestResourceUrl("a.jar"));
+ assertArrayContains(urls, TestUtil.getTestResourceUrl("b.jar"));
+ assertArrayContains(urls, TestUtil.getTestResourceUrl("c.jar"));
+ }
+
+ @Test
+ void testConfigureOptionallyNonExistent() throws Exception {
+ this.configurator.configure(getConfigPath("optionally-nonexistent.conf"));
+
+ assertEquals("org.apache.maven.app.App", this.launcher.getMainClassName());
+
+ assertEquals("opt", this.launcher.getMainRealmName());
+
+ ClassWorld world = this.launcher.getWorld();
+
+ Collection<ClassRealm> realms = world.getRealms();
+
+ assertEquals(1, realms.size());
+
+ assertNotNull(world.getRealm("opt"));
+
+ ClassRealm optRealm = world.getRealm("opt");
+
+ URL[] urls = optRealm.getURLs();
+
+ assertEquals(0, urls.length, "no urls");
+ }
+
+ @Test
+ void testConfigureOptionallyExistent() throws Exception {
+ this.configurator.configure(getConfigPath("optionally-existent.conf"));
+
+ assertEquals("org.apache.maven.app.App", this.launcher.getMainClassName());
+
+ assertEquals("opt", this.launcher.getMainRealmName());
+
+ ClassWorld world = this.launcher.getWorld();
+
+ Collection<ClassRealm> realms = world.getRealms();
+
+ assertEquals(1, realms.size());
+
+ assertNotNull(world.getRealm("opt"));
+
+ ClassRealm optRealm = world.getRealm("opt");
+
+ URL[] urls = optRealm.getURLs();
+
+ assertEquals(1, urls.length, "one url");
+
+ assertSame(null, optRealm.getImportClassLoader("jakarta.xml.bind.JAXBException"));
+ }
+
+ @Test
+ void testConfigureUnhandled() throws Exception {
+ try {
+ this.configurator.configure(getConfigPath("unhandled.conf"));
+ fail("throw ConfigurationException");
+ } catch (ConfigurationException e) {
+ // expected and correct
+ assertTrue(e.getMessage().startsWith("Unhandled configuration"));
+ }
+ }
+
+ @Test
+ void testSetUsingExistent() throws Exception {
+ assertNull(System.getProperty("set.using.existent"));
+
+ this.configurator.configure(getConfigPath("set-using-existent.conf"));
+
+ assertEquals("testSetUsingExistent", System.getProperty("set.using.existent"));
+ }
+
+ @Test
+ void testSetUsingNonExistent() throws Exception {
+ assertNull(System.getProperty("set.using.nonexistent"));
+
+ this.configurator.configure(getConfigPath("set-using-nonexistent.conf"));
+
+ assertNull(System.getProperty("set.using.nonexistent"));
+ }
+
+ @Test
+ void testSetUsingNonExistentDefault() throws Exception {
+ assertNull(System.getProperty("set.using.nonexistent.default"));
+
+ this.configurator.configure(getConfigPath("set-using-nonexistent.conf"));
+
+ assertEquals("testSetUsingNonExistentDefault", System.getProperty("set.using.nonexistent.default"));
+ }
+
+ @Test
+ void testSetUsingNonExistentOverride() throws Exception {
+ assertNull(System.getProperty("set.using.default"));
+ System.setProperty("set.using.default", "testSetUsingNonExistentOverride");
+
+ this.configurator.configure(getConfigPath("set-using-nonexistent.conf"));
+
+ assertEquals("testSetUsingNonExistentOverride", System.getProperty("set.using.default"));
+ }
+
+ @Test
+ void testSetUsingExistentOverride() throws Exception {
+ assertNull(System.getProperty("set.using.existent"));
+ System.setProperty("set.using.existent", "testSetUsingExistentOverride");
+
+ this.configurator.configure(getConfigPath("set-using-existent.conf"));
+
+ assertEquals("testSetUsingExistentOverride", System.getProperty("set.using.existent"));
+ }
+
+ @Test
+ void testSetUsingExistentDefault() throws Exception {
+ assertNull(System.getProperty("set.using.default"));
+
+ this.configurator.configure(getConfigPath("set-using-existent.conf"));
+
+ assertEquals("testSetUsingExistentDefault", System.getProperty("set.using.default"));
+ }
+
+ @Test
+ void testSetUsingMissingDefault() throws Exception {
+ assertNull(System.getProperty("set.using.missing"));
+
+ this.configurator.configure(getConfigPath("set-using-missing.conf"));
+
+ assertEquals("testSetUsingMissingDefault", System.getProperty("set.using.missing"));
+ }
+
+ @Test
+ void testSetUsingMissingOverride() throws Exception {
+ assertNull(System.getProperty("set.using.missing"));
+ System.setProperty("set.using.missing", "testSetUsingMissingOverride");
+
+ this.configurator.configure(getConfigPath("set-using-missing.conf"));
+
+ assertEquals("testSetUsingMissingOverride", System.getProperty("set.using.missing"));
+ }
+
+ @Test
+ void testSetUsingFilteredDefault() throws Exception {
+ assertNull(System.getProperty("set.using.filtered.default"));
+
+ this.configurator.configure(getConfigPath("set-using-missing.conf"));
+
+ assertEquals(System.getProperty("user.home") + "/m2", System.getProperty("set.using.filtered.default"));
+ }
+
+ @Test
+ void testFromFromFrom() throws Exception {
+ this.configurator.configure(getConfigPath("valid-from-from-from.conf"));
+
+ assertEquals("com.from.from.from.Main", this.launcher.getMainClassName());
+
+ assertEquals("from", this.launcher.getMainRealmName());
+
+ ClassWorld world = this.launcher.getWorld();
+
+ ClassRealm antRealm = world.getRealm("ant");
+ Collection<ClassRealm> antImportRealms = antRealm.getImportRealms();
+ assertEquals(1, antImportRealms.size());
+ assertEquals("from", antImportRealms.stream().findFirst().get().getId());
+
+ ClassRealm mavenRealm = world.getRealm("maven");
+ Collection<ClassRealm> mavenImportRealms = mavenRealm.getImportRealms();
+ assertEquals(1, mavenImportRealms.size());
+ assertEquals("from", mavenImportRealms.stream().findFirst().get().getId());
+
+ ClassRealm globRealm = world.getRealm("glob");
+ Collection<ClassRealm> globImportRealms = globRealm.getImportRealms();
+ assertEquals(0, globImportRealms.size());
+
+ ClassRealm fromRealm = world.getRealm("from");
+ Collection<ClassRealm> fromImportRealms = fromRealm.getImportRealms();
+ assertEquals(0, fromImportRealms.size());
+ }
+
+ private FileInputStream getConfigPath(String name) throws Exception {
+ return new FileInputStream(new File(new File(TestUtil.getBasedir(), "src/test/test-data"), name));
+ }
+
+ private void assertArrayContains(URL[] array, URL url) {
+ for (URL value : array) {
+ if (url.equals(value)) {
+ return;
+ }
+ }
+ fail("URL (" + url + ") not found in array of URLs");
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/LauncherTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/LauncherTest.java
new file mode 100644
index 0000000000..39ca08cb73
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/LauncherTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.launcher;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+import java.io.File;
+import java.io.FileInputStream;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.TestUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class LauncherTest extends AbstractClassWorldsTestCase {
+ private Launcher launcher;
+
+ @BeforeEach
+ public void setUp() {
+ this.launcher = new Launcher();
+
+ this.launcher.setSystemClassLoader(Thread.currentThread().getContextClassLoader());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.launcher = null;
+ }
+
+ @Test
+ void testConfigureValid() throws Exception {
+ launcher.configure(getConfigPath("valid-launch.conf"));
+
+ Class<?> mainClass = launcher.getMainClass();
+
+ assertNotNull(mainClass);
+
+ assertEquals("a.A", mainClass.getName());
+
+ assertEquals("app", launcher.getMainRealm().getId());
+ }
+
+ @Test
+ void testLaunchValidStandard() throws Exception {
+ launcher.configure(getConfigPath("valid-launch.conf"));
+
+ launcher.launch(new String[] {});
+ }
+
+ @Test
+ void testLaunchValidStandardExitCode() throws Exception {
+ launcher.configure(getConfigPath("valid-launch-exitCode.conf"));
+
+ launcher.launch(new String[] {});
+
+ assertEquals(15, launcher.getExitCode(), "check exit code");
+ }
+
+ @Test
+ void testLaunchValidEnhanced() throws Exception {
+ launcher.configure(getConfigPath("valid-enh-launch.conf"));
+
+ launcher.launch(new String[] {});
+ }
+
+ @Test
+ void testLaunchValidEnhancedExitCode() throws Exception {
+ launcher.configure(getConfigPath("valid-enh-launch-exitCode.conf"));
+
+ launcher.launch(new String[] {});
+
+ assertEquals(45, launcher.getExitCode(), "check exit code");
+ }
+
+ @Test
+ void testLaunchNoSuchMethod() throws Exception {
+ launcher.configure(getConfigPath("launch-nomethod.conf"));
+
+ try {
+ launcher.launch(new String[] {});
+ fail("should have thrown NoSuchMethodException");
+ } catch (NoSuchMethodException e) {
+ // expected and correct
+ }
+ }
+
+ @Test
+ void testLaunchClassNotFound() throws Exception {
+ launcher.configure(getConfigPath("launch-noclass.conf"));
+
+ try {
+ launcher.launch(new String[] {});
+ fail("throw ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+ }
+
+ private FileInputStream getConfigPath(String name) throws Exception {
+ String basedir = TestUtil.getBasedir();
+
+ return new FileInputStream(new File(new File(basedir, "src/test/test-data"), name));
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java
new file mode 100644
index 0000000000..fb2dd15063
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java
@@ -0,0 +1,373 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ClassRealmImplTest extends AbstractClassWorldsTestCase {
+ private ClassWorld world;
+
+ @BeforeEach
+ public void setUp() {
+ this.world = new ClassWorld();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.world = null;
+ }
+
+ @Test
+ void testNewRealm() throws Exception {
+ ClassRealm realm = this.world.newRealm("foo");
+ assertNotNull(realm);
+ assertSame(this.world, realm.getWorld());
+ assertEquals("foo", realm.getId());
+ }
+
+ @Test
+ void testLocateSourceRealmNoImports() {
+ ClassRealm realm = new ClassRealm(this.world, "foo", null);
+ assertSame(null, realm.getImportClassLoader("com.werken.Stuff"));
+ }
+
+ @Test
+ void testLocateSourceRealmSimpleImport() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ ClassRealm werkflowRealm = this.world.newRealm("werkflow");
+ mainRealm.importFrom("werkflow", "com.werken.werkflow");
+
+ assertSame(werkflowRealm, mainRealm.getImportClassLoader("com.werken.werkflow.WerkflowEngine"));
+ assertSame(werkflowRealm, mainRealm.getImportClassLoader("com/werken/werkflow/some.properties"));
+ assertSame(werkflowRealm, mainRealm.getImportClassLoader("com.werken.werkflow.process.ProcessManager"));
+ assertSame(null, mainRealm.getImportClassLoader("com.werken.blissed.Process"));
+ assertSame(null, mainRealm.getImportClassLoader("java.lang.Object"));
+ assertSame(null, mainRealm.getImportClassLoader("NoviceProgrammerClass"));
+ }
+
+ @Test
+ void testLocateSourceRealmMultipleImport() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ ClassRealm werkflowRealm = this.world.newRealm("werkflow");
+ ClassRealm blissedRealm = this.world.newRealm("blissed");
+ mainRealm.importFrom("werkflow", "com.werken.werkflow");
+ mainRealm.importFrom("blissed", "com.werken.blissed");
+
+ assertSame(werkflowRealm, mainRealm.getImportClassLoader("com.werken.werkflow.WerkflowEngine"));
+ assertSame(werkflowRealm, mainRealm.getImportClassLoader("com.werken.werkflow.process.ProcessManager"));
+ assertSame(blissedRealm, mainRealm.getImportClassLoader("com.werken.blissed.Process"));
+ assertSame(blissedRealm, mainRealm.getImportClassLoader("com.werken.blissed.guard.BooleanGuard"));
+ assertSame(null, mainRealm.getImportClassLoader("java.lang.Object"));
+ assertSame(null, mainRealm.getImportClassLoader("NoviceProgrammerClass"));
+ }
+
+ @Test
+ void testLocateSourceRealmHierachy() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ ClassRealm fooRealm = this.world.newRealm("foo");
+ ClassRealm fooBarRealm = this.world.newRealm("fooBar");
+ ClassRealm fooBarBazRealm = this.world.newRealm("fooBarBaz");
+ mainRealm.importFrom("foo", "foo");
+ mainRealm.importFrom("fooBar", "foo.bar");
+ mainRealm.importFrom("fooBarBaz", "foo.bar.baz");
+
+ assertSame(fooRealm, mainRealm.getImportClassLoader("foo.Goober"));
+ assertSame(fooRealm, mainRealm.getImportClassLoader("foo.cheese.Goober"));
+ assertSame(fooBarRealm, mainRealm.getImportClassLoader("foo.bar.Goober"));
+ assertSame(fooBarRealm, mainRealm.getImportClassLoader("foo.bar.cheese.Goober"));
+ assertSame(fooBarBazRealm, mainRealm.getImportClassLoader("foo.bar.baz.Goober"));
+ assertSame(fooBarBazRealm, mainRealm.getImportClassLoader("foo.bar.baz.cheese.Goober"));
+ assertSame(null, mainRealm.getImportClassLoader("java.lang.Object"));
+ assertSame(null, mainRealm.getImportClassLoader("NoviceProgrammerClass"));
+ }
+
+ @Test
+ void testLocateSourceRealmHierachyReverse() throws Exception {
+ ClassRealm fooBarBazRealm = this.world.newRealm("fooBarBaz");
+ ClassRealm fooBarRealm = this.world.newRealm("fooBar");
+ ClassRealm fooRealm = this.world.newRealm("foo");
+ ClassRealm mainRealm = this.world.newRealm("main");
+ mainRealm.importFrom("fooBarBaz", "foo.bar.baz");
+ mainRealm.importFrom("fooBar", "foo.bar");
+ mainRealm.importFrom("foo", "foo");
+
+ assertSame(fooRealm, mainRealm.getImportClassLoader("foo.Goober"));
+ assertSame(fooRealm, mainRealm.getImportClassLoader("foo.cheese.Goober"));
+ assertSame(fooBarRealm, mainRealm.getImportClassLoader("foo.bar.Goober"));
+ assertSame(fooBarRealm, mainRealm.getImportClassLoader("foo.bar.cheese.Goober"));
+ assertSame(fooBarBazRealm, mainRealm.getImportClassLoader("foo.bar.baz.Goober"));
+ assertSame(fooBarBazRealm, mainRealm.getImportClassLoader("foo.bar.baz.cheese.Goober"));
+ assertSame(null, mainRealm.getImportClassLoader("java.lang.Object"));
+ assertSame(null, mainRealm.getImportClassLoader("NoviceProgrammerClass"));
+ }
+
+ @Test
+ void testLoadClassSystemClass() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ Class<?> cls = mainRealm.loadClass("java.lang.Object");
+ assertNotNull(cls);
+ }
+
+ @Test
+ void testLoadClassNonSystemClass() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ try {
+ Class<?> c = mainRealm.loadClass("com.werken.projectz.UberThing");
+ System.out.println("c = " + c);
+ fail("A ClassNotFoundException should be thrown!");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+ }
+
+ @Test
+ void testLoadClassClassWorldsClass() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ Class<?> cls = mainRealm.loadClass("org.codehaus.plexus.classworlds.ClassWorld");
+ assertNotNull(cls);
+ assertSame(ClassWorld.class, cls);
+ }
+
+ @Test
+ void testLoadClassLocal() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ try {
+ mainRealm.loadClass("a.A");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+
+ mainRealm.addURL(getJarUrl("a.jar"));
+ Class<?> classA = mainRealm.loadClass("a.A");
+ assertNotNull(classA);
+
+ ClassRealm otherRealm = this.world.newRealm("other");
+ try {
+ otherRealm.loadClass("a.A");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+ }
+
+ @Test
+ void testLoadClassImported() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ ClassRealm realmA = this.world.newRealm("realmA");
+
+ try {
+ realmA.loadClass("a.A");
+ fail("realmA.loadClass(a.A) should have thrown a ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+
+ realmA.addURL(getJarUrl("a.jar"));
+
+ try {
+ mainRealm.loadClass("a.A");
+ fail("mainRealm.loadClass(a.A) should have thrown a ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+
+ mainRealm.importFrom("realmA", "a");
+
+ Class<?> classA = realmA.loadClass("a.A");
+ assertNotNull(classA);
+ assertEquals(realmA, classA.getClassLoader());
+
+ Class<?> classMain = mainRealm.loadClass("a.A");
+ assertNotNull(classMain);
+ assertEquals(realmA, classMain.getClassLoader());
+ assertSame(classA, classMain);
+ }
+
+ @Test
+ void testLoadClassPackage() throws Exception {
+ ClassRealm realmA = this.world.newRealm("realmA");
+ realmA.addURL(getJarUrl("a.jar"));
+
+ Class<?> clazz = realmA.loadClass("a.A");
+ assertNotNull(clazz);
+ assertEquals("a.A", clazz.getName());
+
+ Package p = clazz.getPackage();
+ assertNotNull(p);
+ assertEquals("a", p.getName(), "p.getName()");
+ }
+
+ @Test
+ void testLoadClassComplex() throws Exception {
+ ClassRealm realmA = this.world.newRealm("realmA");
+ ClassRealm realmB = this.world.newRealm("realmB");
+ ClassRealm realmC = this.world.newRealm("realmC");
+ realmA.addURL(getJarUrl("a.jar"));
+ realmB.addURL(getJarUrl("b.jar"));
+ realmC.addURL(getJarUrl("c.jar"));
+ realmC.importFrom("realmA", "a");
+ realmC.importFrom("realmB", "b");
+ realmA.importFrom("realmC", "c");
+
+ Class<?> classAA = realmA.loadClass("a.A");
+ Class<?> classBB = realmB.loadClass("b.B");
+ Class<?> classCC = realmC.loadClass("c.C");
+ assertNotNull(classAA);
+ assertNotNull(classBB);
+ assertNotNull(classCC);
+ assertEquals(realmA, classAA.getClassLoader());
+ assertEquals(realmB, classBB.getClassLoader());
+ assertEquals(realmC, classCC.getClassLoader());
+
+ // load from C
+ Class<?> classAC = realmC.loadClass("a.A");
+ assertNotNull(classAC);
+ assertSame(classAA, classAC);
+ assertEquals(realmA, classAC.getClassLoader());
+
+ Class<?> classBC = realmC.loadClass("b.B");
+ assertNotNull(classBC);
+ assertSame(classBB, classBC);
+ assertEquals(realmB, classBC.getClassLoader());
+
+ // load from A
+ Class<?> classCA = realmA.loadClass("c.C");
+ assertNotNull(classCA);
+ assertSame(classCC, classCA);
+ assertEquals(realmC, classCA.getClassLoader());
+
+ try {
+ realmA.loadClass("b.B");
+ fail("throw ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+
+ // load from B
+ try {
+ realmB.loadClass("a.A");
+ fail("throw ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+
+ try {
+ realmB.loadClass("c.C");
+ fail("throw ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected and correct
+ }
+ }
+
+ @Test
+ void testLoadClassClassWorldsClassRepeatedly() throws Exception {
+ ClassRealm mainRealm = this.world.newRealm("main");
+ for (int i = 0; i < 100; i++) {
+ Class<?> cls = mainRealm.loadClass("org.codehaus.plexus.classworlds.ClassWorld");
+ assertNotNull(cls);
+ assertSame(ClassWorld.class, cls);
+ }
+ }
+
+ @Test
+ void testLoadClassWithModuleNameJava9() {
+ final ExtendedClassRealm mainRealm = new ExtendedClassRealm(world);
+ mainRealm.addURL(getJarUrl("a.jar"));
+ assertNotNull(mainRealm.simulateLoadClassFromModule("a.A"));
+ }
+
+ @Test
+ void testGetResourcesBaseBeforeSelf() throws Exception {
+ String resource = "common.properties";
+
+ ClassRealm base = this.world.newRealm("realmA");
+ base.addURL(getJarUrl("a.jar"));
+ URL baseUrl = base.getResource(resource);
+ assertNotNull(baseUrl);
+
+ ClassRealm sub = this.world.newRealm("realmB", base);
+ sub.addURL(getJarUrl("b.jar"));
+ URL subUrl = sub.getResource(resource);
+ assertNotNull(subUrl);
+ assertEquals(baseUrl, subUrl);
+
+ List<String> urls = new ArrayList<>();
+ for (URL url : Collections.list(sub.getResources(resource))) {
+ String path = url.toString();
+ path = path.substring(path.lastIndexOf('/', path.lastIndexOf(".jar!")));
+ urls.add(path);
+ }
+ assertEquals(Arrays.asList("/a.jar!/common.properties", "/b.jar!/common.properties"), urls);
+ }
+
+ @Test
+ void testGetResourcesSelfBeforeParent() throws Exception {
+ String resource = "common.properties";
+
+ ClassRealm parent = this.world.newRealm("realmA");
+ parent.addURL(getJarUrl("a.jar"));
+ URL parentUrl = parent.getResource(resource);
+ assertNotNull(parentUrl);
+
+ ClassRealm child = parent.createChildRealm("realmB");
+ child.addURL(getJarUrl("b.jar"));
+ URL childUrl = child.getResource(resource);
+ assertNotNull(childUrl);
+
+ List<URL> urls = Collections.list(child.getResources(resource));
+ assertNotNull(urls);
+ assertEquals(Arrays.asList(childUrl, parentUrl), urls);
+ }
+
+ /**
+ * Simulates new {@code java.lang.ClassLoader#findClass(String,String)} introduced with Java 9.
+ * It is reversed in terms of inheritance but enables to simulate the same behavior in these tests.
+ * @see <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)">ClassLoader#findClass(String,String)</a>
+ */
+ private static class ExtendedClassRealm extends ClassRealm {
+
+ ExtendedClassRealm(final ClassWorld world) {
+ super(world, "java9", Thread.currentThread().getContextClassLoader());
+ }
+
+ public Class<?> simulateLoadClassFromModule(final String name) {
+ synchronized (getClassLoadingLock(name)) {
+ Class<?> c = findLoadedClass(name);
+ if (c == null) {
+ c = findClass(null, name);
+ }
+ return c;
+ }
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java
new file mode 100644
index 0000000000..c79468c973
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java
@@ -0,0 +1,346 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class DefaultClassRealmTest extends AbstractClassWorldsTestCase {
+ // ----------------------------------------------------------------------
+ // Class testing
+ // ----------------------------------------------------------------------
+
+ @Test
+ void testLoadClassFromRealm() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("component0-1.0.jar"));
+
+ loadClass(mainRealm, "org.codehaus.plexus.Component0");
+ }
+
+ @Test
+ void testLoadClassFromChildRealmWhereClassIsLocatedInParentRealm() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("component0-1.0.jar"));
+
+ ClassRealm childRealm = mainRealm.createChildRealm("child");
+
+ loadClass(childRealm, "org.codehaus.plexus.Component0");
+ }
+
+ @Test
+ void testLoadClassFromChildRealmWhereClassIsLocatedInGrantParentRealm() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("component0-1.0.jar"));
+
+ ClassRealm childRealm = mainRealm.createChildRealm("child");
+
+ ClassRealm grandchildRealm = childRealm.createChildRealm("grandchild");
+
+ loadClass(grandchildRealm, "org.codehaus.plexus.Component0");
+ }
+
+ @Test
+ void testLoadClassFromChildRealmWhereClassIsLocatedInBothChildRealmAndParentRealm() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "parent", null);
+
+ mainRealm.addURL(getJarUrl("component5-1.0.jar"));
+
+ ClassRealm childRealm = mainRealm.createChildRealm("child");
+
+ childRealm.addURL(getJarUrl("component5-2.0.jar"));
+
+ Class<?> cls = loadClass(childRealm, "test.Component5");
+
+ assertSame(childRealm, cls.getClassLoader());
+ assertEquals(1, cls.getMethods().length);
+ assertEquals("printNew", cls.getMethods()[0].getName());
+ }
+
+ @Test
+ void testLoadNonExistentClass() {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("component0-1.0.jar"));
+
+ try {
+ mainRealm.loadClass("org.foo.bar.NonExistentClass");
+
+ fail("A ClassNotFoundException should have been thrown!");
+ } catch (ClassNotFoundException e) {
+ // expected
+ }
+ }
+
+ @Test
+ void testImport() throws Exception {
+ ClassWorld world = new ClassWorld();
+
+ ClassRealm r0 = world.newRealm("r0");
+
+ ClassRealm r1 = world.newRealm("r1");
+
+ r0.addURL(getJarUrl("component0-1.0.jar"));
+
+ r1.importFrom("r0", "org.codehaus.plexus");
+
+ loadClass(r1, "org.codehaus.plexus.Component0");
+ }
+
+ @Test
+ void testParentImport() throws Exception {
+ ClassWorld world = new ClassWorld();
+
+ ClassRealm parent = world.newRealm("parent");
+
+ ClassRealm child = world.newRealm("child");
+
+ parent.addURL(getJarUrl("component0-1.0.jar"));
+
+ child.setParentRealm(parent);
+
+ Class<?> type = loadClass(child, "org.codehaus.plexus.Component0");
+
+ child.importFromParent("non-existing");
+
+ assertSame(null, loadClassOrNull(child, "org.codehaus.plexus.Component0"));
+
+ child.importFromParent("org.codehaus.plexus");
+
+ assertSame(type, loadClass(child, "org.codehaus.plexus.Component0"));
+ }
+
+ @Test
+ void testLoadClassFromBaseClassLoaderBeforeSelf() throws Exception {
+ ClassWorld world = new ClassWorld();
+
+ ClassRealm base = world.newRealm("base");
+
+ base.addURL(getJarUrl("a.jar"));
+
+ ClassRealm child = world.newRealm("child", base);
+
+ child.addURL(getJarUrl("a.jar"));
+
+ Class<?> baseClass = loadClass(base, "a.A");
+ Class<?> childClass = loadClass(child, "a.A");
+
+ assertSame(base, baseClass.getClassLoader());
+ assertSame(base, childClass.getClassLoader());
+ assertSame(baseClass, childClass);
+ }
+
+ @Test
+ void testLoadClassFromRealmWithCircularClassReferences() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("circular-0.1.jar"));
+
+ /*
+ * This was reported to fail with a ClassCircularityError in IBM JDK 1.5.0-SR2, 1.5.0-SR7 and 1.6.0-SR2. It
+ * works in IBM JDK 1.5.0-SR10 and 1.6.0-SR6.
+ */
+ loadClass(mainRealm, "A$C");
+ }
+
+ // ----------------------------------------------------------------------
+ // Resource testing
+ // ----------------------------------------------------------------------
+
+ @Test
+ void testResource() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("component0-1.0.jar"));
+
+ getResource(mainRealm, "META-INF/plexus/components.xml");
+ }
+
+ @Test
+ void testMalformedResource() throws Exception {
+ URL jarUrl = getJarUrl("component0-1.0.jar");
+
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(jarUrl);
+
+ ClassLoader officialClassLoader = new URLClassLoader(new URL[] {jarUrl});
+
+ String resource = "META-INF/plexus/components.xml";
+
+ assertNotNull(mainRealm.getResource(resource));
+ assertNotNull(officialClassLoader.getResource(resource));
+
+ /*
+ * NOTE: Resource names with a leading slash are invalid when passed to a class loader and must not be found!
+ * One can use a leading slash in Class.getResource() but not in ClassLoader.getResource().
+ */
+
+ assertSame(null, mainRealm.getResource("/" + resource));
+ assertSame(null, officialClassLoader.getResource("/" + resource));
+ }
+
+ @Test
+ void testFindResourceOnlyScansSelf() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("a.jar"));
+
+ ClassRealm childRealm = mainRealm.createChildRealm("child");
+
+ childRealm.addURL(getJarUrl("b.jar"));
+
+ assertNotNull(childRealm.getResource("a.properties"));
+ assertNotNull(childRealm.getResource("b.properties"));
+
+ assertNull(childRealm.findResource("a.properties"));
+
+ assertNotNull(childRealm.findResource("b.properties"));
+ }
+
+ @Test
+ void testFindResourcesOnlyScansSelf() throws Exception {
+ ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null);
+
+ mainRealm.addURL(getJarUrl("a.jar"));
+
+ ClassRealm childRealm = mainRealm.createChildRealm("child");
+
+ childRealm.addURL(getJarUrl("b.jar"));
+
+ assertTrue(childRealm.getResources("a.properties").hasMoreElements());
+ assertTrue(childRealm.getResources("b.properties").hasMoreElements());
+
+ assertFalse(childRealm.findResources("a.properties").hasMoreElements());
+
+ assertTrue(childRealm.findResources("b.properties").hasMoreElements());
+ }
+
+ /** Should never deadlock. Ever */
+ @Test
+ void testParallelDeadlockClassRealm() throws InterruptedException {
+ for (int i = 0; i < 100; i++) {
+ doOneDeadlockAttempt();
+ }
+ }
+
+ private void doOneDeadlockAttempt() throws InterruptedException {
+ // Deadlock sample graciously ripped from http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html
+ final ClassRealm cl1 = new ClassRealm(new ClassWorld(), "cl1", null);
+ final ClassRealm cl2 = new ClassRealm(new ClassWorld(), "cl2", cl1);
+ cl1.setParentRealm(cl2);
+ cl1.addURL(getJarUrl("deadlock.jar"));
+ cl2.addURL(getJarUrl("deadlock.jar"));
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ Runnable r1 = () -> {
+ try {
+ latch.await();
+ cl1.loadClass("deadlock.A");
+ } catch (ClassNotFoundException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable r2 = () -> {
+ try {
+ latch.await();
+ cl1.loadClass("deadlock.C");
+ } catch (ClassNotFoundException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Thread thread = new Thread(r1);
+ thread.start();
+ Thread thread1 = new Thread(r2);
+ thread1.start();
+ latch.countDown();
+ thread.join();
+ thread1.join();
+ }
+
+ // ----------------------------------------------------------------------
+ //
+ // ----------------------------------------------------------------------
+
+ private Class<?> loadClassOrNull(ClassRealm realm, String name) {
+ try {
+ return loadClass(realm, name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ private Class<?> loadClass(ClassRealm realm, String name) throws ClassNotFoundException {
+ Class<?> cls = realm.loadClass(name);
+
+ /*
+ * NOTE: Load the class both directly from the realm and indirectly from an (ordinary) child class loader which
+ * uses the specified class realm for parent delegation. The child class loader itself has no additional class
+ * path entries but relies entirely on the provided class realm. Hence, the created child class loader should in
+ * theory be able to load exactly the same classes/resources as the underlying class realm. In practice, it will
+ * test that class realms properly integrate into the standard Java class loader hierarchy.
+ */
+ ClassLoader childLoader = new URLClassLoader(new URL[0], realm);
+ assertEquals(cls, childLoader.loadClass(name));
+
+ return cls;
+ }
+
+ private void getResource(ClassRealm realm, String name) throws Exception {
+ ClassLoader childLoader = new URLClassLoader(new URL[0], realm);
+ assertNotNull(realm.getResource(name));
+ assertEquals(realm.getResource(name), childLoader.getResource(name));
+ assertEquals(Collections.list(realm.getResources(name)), Collections.list(childLoader.getResources(name)));
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/EntryTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/EntryTest.java
new file mode 100644
index 0000000000..f0115df603
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/EntryTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author <a href="bwalding@jakarta.org">Ben Walding</a>
+ */
+class EntryTest extends AbstractClassWorldsTestCase {
+
+ @Test
+ void testCompareTo() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry1 = new Entry(r, "org.test");
+ Entry entry2 = new Entry(r, "org.test.impl");
+
+ assertTrue(entry1.compareTo(entry2) > 0, "org.test > org.test.impl");
+ }
+
+ /**
+ * Tests the equality is realm independant
+ */
+ @Test
+ void testEquals() throws DuplicateRealmException {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r1 = cw.newRealm("test1");
+ ClassRealm r2 = cw.newRealm("test2");
+
+ Entry entry1 = new Entry(r1, "org.test");
+ Entry entry2 = new Entry(r2, "org.test");
+
+ assertEquals(entry1, entry2, "entry1 == entry2");
+ assertEquals(entry1.hashCode(), entry2.hashCode(), "entry1.hashCode() == entry2.hashCode()");
+ }
+
+ @Test
+ void testMatchesClassByPackageImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test");
+
+ assertTrue(entry.matches("org.test.MyClass"));
+ assertTrue(entry.matches("org.test.MyClass$NestedClass"));
+ assertTrue(entry.matches("org.test.MyClassUtils"));
+ assertTrue(entry.matches("org.test.impl.MyClass"));
+ assertFalse(entry.matches("org.tests.AnotherClass"));
+ }
+
+ @Test
+ void testMatchesClassByClassImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test.MyClass");
+
+ assertTrue(entry.matches("org.test.MyClass"));
+ assertTrue(entry.matches("org.test.MyClass$NestedClass"));
+ assertFalse(entry.matches("org.test.MyClassUtils"));
+ assertFalse(entry.matches("org.test.AnotherClass"));
+ }
+
+ @Test
+ void testMatchesResourceByPackageImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test");
+
+ assertTrue(entry.matches("org/test/MyClass.class"));
+ assertTrue(entry.matches("org/test/MyClass$NestedClass.class"));
+ assertTrue(entry.matches("org/test/MyClasses.properties"));
+ assertTrue(entry.matches("org/test/impl/MyClass.class"));
+ assertFalse(entry.matches("org/tests/AnotherClass.class"));
+ }
+
+ @Test
+ void testMatchesResourceByClassImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test.MyClass");
+
+ assertTrue(entry.matches("org/test/MyClass.class"));
+ assertTrue(entry.matches("org/test/MyClass$NestedClass.class"));
+ assertFalse(entry.matches("org/test/MyClass.properties"));
+ assertFalse(entry.matches("org/test/AnotherClass"));
+ }
+
+ @Test
+ void testMatchesAllImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "");
+
+ assertTrue(entry.matches("org.test.MyClass"));
+ assertTrue(entry.matches("org.test.MyClass$NestedClass"));
+ assertTrue(entry.matches("org/test/MyClass.class"));
+ assertTrue(entry.matches("org/test/MyClass.properties"));
+ }
+
+ @Test
+ void testMatchesResourceByResourceImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry1 = new Entry(r, "some.properties");
+
+ assertTrue(entry1.matches("some.properties"));
+ assertFalse(entry1.matches("other.properties"));
+
+ Entry entry2 = new Entry(r, "org/test/some.properties");
+
+ assertTrue(entry2.matches("org/test/some.properties"));
+ assertFalse(entry2.matches("org/test/other.properties"));
+ }
+
+ @Test
+ void testMatchesClassByExactPackageImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test.*");
+
+ assertTrue(entry.matches("org.test.MyClass"));
+ assertTrue(entry.matches("org.test.MyClass$NestedClass"));
+ assertTrue(entry.matches("org.test.MyClassUtils"));
+ assertFalse(entry.matches("org.test.impl.MyClass"));
+ assertFalse(entry.matches("org.tests.AnotherClass"));
+ }
+
+ @Test
+ void testMatchesResourceByExactPackageImport() throws Exception {
+ ClassWorld cw = new ClassWorld();
+ ClassRealm r = cw.newRealm("test1");
+
+ Entry entry = new Entry(r, "org.test.*");
+
+ assertTrue(entry.matches("org/test/MyClass.class"));
+ assertTrue(entry.matches("org/test/MyClass$NestedClass.class"));
+ assertTrue(entry.matches("org/test/MyClasses.properties"));
+ assertFalse(entry.matches("org/test/impl/MyClass.class"));
+ assertFalse(entry.matches("org/tests/AnotherClass.class"));
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java
new file mode 100644
index 0000000000..a85076da34
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.realm;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FilteredClassRealmTest extends AbstractClassWorldsTestCase {
+ private ClassWorld world;
+ private ClassRealm realmA;
+
+ @BeforeEach
+ public void setUp() throws DuplicateRealmException {
+ this.world = new ClassWorld();
+ // only allow loading resources whose names start with "a."
+ Set<String> allowedResourcePrefixes = new HashSet<>();
+ allowedResourcePrefixes.add("a.");
+ allowedResourcePrefixes.add("a/Aa");
+ realmA = this.world.newRealm(
+ "realmA",
+ getClass().getClassLoader(),
+ s -> allowedResourcePrefixes.stream().anyMatch(s::startsWith));
+ }
+
+ @Test
+ void testLoadResources() throws Exception {
+ realmA.addURL(getJarUrl("a.jar"));
+ assertNull(realmA.getResource("common.properties"));
+ assertFalse(realmA.getResources("common.properties").hasMoreElements());
+
+ assertNotNull(realmA.getResource("a.properties"));
+ assertTrue(realmA.getResources("a.properties").hasMoreElements());
+ }
+
+ @Test
+ void testLoadClass() throws ClassNotFoundException {
+ assertThrows(ClassNotFoundException.class, () -> realmA.loadClass("a.Aa"));
+ realmA.addURL(getJarUrl("a.jar"));
+
+ assertNotNull(realmA.loadClass("a.Aa"));
+ assertThrows(ClassNotFoundException.class, () -> realmA.loadClass("a.A"));
+
+ assertNotNull(realmA.loadClass("a.Aa"));
+ assertThrows(ClassNotFoundException.class, () -> realmA.loadClass("a.A"));
+ }
+
+ @Test
+ void testLoadClassWithModule() throws IOException {
+ try (ExtendedFilteredClassRealm realmA = new ExtendedFilteredClassRealm(world, s -> s.startsWith("a/Aa"))) {
+ realmA.addURL(getJarUrl("a.jar"));
+ assertNotNull(realmA.simulateLoadClassFromModule("a.Aa"));
+ assertNull(realmA.simulateLoadClassFromModule("a.A"));
+ }
+ }
+
+ /**
+ * Simulates new {@code java.lang.ClassLoader#findClass(String,String)} introduced with Java 9.
+ * It is reversed in terms of inheritance but enables to simulate the same behavior in these tests.
+ * @see <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)">ClassLoader#findClass(String,String)</a>
+ * @see ClassRealmImplTest.ExtendedClassRealm
+ */
+ static class ExtendedFilteredClassRealm extends FilteredClassRealm {
+
+ ExtendedFilteredClassRealm(final ClassWorld world, Predicate<String> filter) {
+ super(filter, world, "java9", Thread.currentThread().getContextClassLoader());
+ }
+
+ public Class<?> simulateLoadClassFromModule(final String name) {
+ synchronized (getClassLoadingLock(name)) {
+ Class<?> c = findLoadedClass(name);
+ if (c == null) {
+ c = findClass(null, name);
+ }
+ return c;
+ }
+ }
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyFactoryTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyFactoryTest.java
new file mode 100644
index 0000000000..6c45242707
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyFactoryTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class StrategyFactoryTest {
+
+ private ClassRealm realm;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ realm = new ClassWorld().newRealm("test");
+ }
+
+ @Test
+ void defaultStrategyIsSelfFirst() {
+ assertInstanceOf(SelfFirstStrategy.class, StrategyFactory.getStrategy(realm));
+ }
+
+ @Test
+ void selfFirstHint() {
+ assertInstanceOf(SelfFirstStrategy.class, StrategyFactory.getStrategy(realm, "self-first"));
+ }
+
+ @Test
+ void defaultHintIsSelfFirst() {
+ assertInstanceOf(SelfFirstStrategy.class, StrategyFactory.getStrategy(realm, "default"));
+ }
+
+ @Test
+ void nullHintIsSelfFirst() {
+ assertInstanceOf(SelfFirstStrategy.class, StrategyFactory.getStrategy(realm, null));
+ }
+
+ @Test
+ void emptyHintIsSelfFirst() {
+ assertInstanceOf(SelfFirstStrategy.class, StrategyFactory.getStrategy(realm, ""));
+ }
+
+ @Test
+ void parentFirstHint() {
+ assertInstanceOf(ParentFirstStrategy.class, StrategyFactory.getStrategy(realm, "parent-first"));
+ }
+
+ @Test
+ void osgiHint() {
+ assertInstanceOf(OsgiBundleStrategy.class, StrategyFactory.getStrategy(realm, "osgi"));
+ }
+
+ @Test
+ void unknownHintThrows() {
+ IllegalArgumentException ex =
+ assertThrows(IllegalArgumentException.class, () -> StrategyFactory.getStrategy(realm, "bogus"));
+ assertInstanceOf(IllegalArgumentException.class, ex);
+ }
+}
diff --git a/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyTest.java b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyTest.java
new file mode 100644
index 0000000000..d8ed59bb7c
--- /dev/null
+++ b/impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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
+ *
+ * http://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.codehaus.plexus.classworlds.strategy;
+
+/*
+ * Copyright 2001-2006 Codehaus Foundation.
+ *
+ * Licensed 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
+ *
+ * http://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.
+ */
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+// jars within jars
+// hierarchy vs graph
+
+class StrategyTest extends AbstractClassWorldsTestCase {
+ private ClassRealm realm;
+
+ private Strategy strategy;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ this.realm = new ClassWorld().newRealm("realm");
+ this.strategy = this.realm.getStrategy();
+ realm.addURL(getJarUrl("component0-1.0.jar"));
+ }
+
+ @Test
+ void testLoadingOfApplicationClass() throws Exception {
+ assertNotNull(strategy.loadClass("org.codehaus.plexus.Component0"));
+ }
+
+ @Test
+ void testLoadingOfApplicationClassThenDoingItAgain() throws Exception {
+ Class<?> c = strategy.loadClass("org.codehaus.plexus.Component0");
+
+ assertNotNull(c);
+
+ c = strategy.loadClass("org.codehaus.plexus.Component0");
+
+ assertNotNull(c);
+ }
+
+ @Test
+ void testLoadingOfSystemClass() throws Exception {
+ assertNotNull(strategy.getRealm().loadClass("java.lang.Object"));
+ }
+
+ @Test
+ void testLoadingOfNonExistentClass() {
+ try {
+ strategy.loadClass("org.codehaus.plexus.NonExistentComponent");
+
+ fail("Should have thrown a ClassNotFoundException!");
+ } catch (ClassNotFoundException e) {
+ // do nothing
+ }
+ }
+
+ @Test
+ void testGetApplicationResource() throws Exception {
+ URL resource = strategy.getResource("META-INF/plexus/components.xml");
+
+ assertNotNull(resource);
+
+ String content = getContent(resource.openStream());
+
+ assertTrue(content.startsWith("<component-set>"));
+ }
+
+ @Test
+ void testGetSystemResource() {
+ assumeTrue(
+ getJavaVersion() < 9.0,
+ "Due to strong encapsulation you cannot get the java/lang/Object.class as resource since Java 9");
+
+ URL resource = strategy.getRealm().getResource("java/lang/Object.class");
+
+ assertNotNull(resource);
+ }
+
+ @Test
+ void testFindResources() throws Exception {
+ realm.addURL(getJarUrl("component1-1.0.jar"));
+
+ Enumeration<URL> e = strategy.getResources("META-INF/plexus/components.xml");
+ assertNotNull(e);
+
+ int resourceCount = 0;
+ while (e.hasMoreElements()) {
+ e.nextElement();
+ resourceCount++;
+ }
+ assertEquals(2, resourceCount);
+ }
+
+ protected String getContent(InputStream in) throws Exception {
+ byte[] buffer = new byte[1024];
+
+ int read;
+
+ StringBuilder content = new StringBuilder();
+
+ while ((read = in.read(buffer, 0, 1024)) >= 0) {
+ content.append(new String(buffer, 0, read));
+ }
+
+ return content.toString();
+ }
+
+ private double getJavaVersion() {
+ return Double.parseDouble(System.getProperty("java.specification.version"));
+ }
+}
diff --git a/impl/maven-classworlds/src/test/test-data/a.properties b/impl/maven-classworlds/src/test/test-data/a.properties
new file mode 100644
index 0000000000..d0e2fad851
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/a.properties
@@ -0,0 +1,18 @@
+################################################################################
+#
+# Copyright 2001-2006 The Codehaus Foundation.
+#
+# Licensed 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
+#
+# http://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.
+#
+
+a properties
diff --git a/impl/maven-classworlds/src/test/test-data/dupe-main.conf b/impl/maven-classworlds/src/test/test-data/dupe-main.conf
new file mode 100644
index 0000000000..ddd111f9a7
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/dupe-main.conf
@@ -0,0 +1,3 @@
+
+main is com.werken.Foo from foo
+main is com.werken.Bar from bar
diff --git a/impl/maven-classworlds/src/test/test-data/dupe-realm.conf b/impl/maven-classworlds/src/test/test-data/dupe-realm.conf
new file mode 100644
index 0000000000..345f51f0e1
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/dupe-realm.conf
@@ -0,0 +1,9 @@
+
+[cheese]
+
+[dupe.realm]
+
+[potatoes]
+
+[dupe.realm]
+
diff --git a/impl/maven-classworlds/src/test/test-data/early-import.conf b/impl/maven-classworlds/src/test/test-data/early-import.conf
new file mode 100644
index 0000000000..23378ecf00
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/early-import.conf
@@ -0,0 +1,2 @@
+
+import org.xml.sax from root
diff --git a/impl/maven-classworlds/src/test/test-data/inheritance.conf b/impl/maven-classworlds/src/test/test-data/inheritance.conf
new file mode 100644
index 0000000000..46f72107e5
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/inheritance.conf
@@ -0,0 +1,18 @@
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from root.maven
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[root]
+load ${basedir}/target/test-jars/a.jar
+
+[root.maven]
+load ${basedir}/target/test-jars/b.jar
+
+[root.maven.plugin]
+load ${basedir}/target/test-jars/c.jar
diff --git a/impl/maven-classworlds/src/test/test-data/launch-noclass.conf b/impl/maven-classworlds/src/test/test-data/launch-noclass.conf
new file mode 100644
index 0000000000..439e24b55b
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/launch-noclass.conf
@@ -0,0 +1,6 @@
+
+main is b.Goober from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
+ load ${basedir}/target/test-jars/b.jar
diff --git a/impl/maven-classworlds/src/test/test-data/launch-nomethod.conf b/impl/maven-classworlds/src/test/test-data/launch-nomethod.conf
new file mode 100644
index 0000000000..674fc499bd
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/launch-nomethod.conf
@@ -0,0 +1,7 @@
+
+main is c.C from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
+ load ${basedir}/target/test-jars/b.jar
+ load ${basedir}/target/test-jars/c.jar
diff --git a/impl/maven-classworlds/src/test/test-data/nested.properties b/impl/maven-classworlds/src/test/test-data/nested.properties
new file mode 100644
index 0000000000..0462a15532
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/nested.properties
@@ -0,0 +1,18 @@
+################################################################################
+#
+# Copyright 2001-2006 The Codehaus Foundation.
+#
+# Licensed 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
+#
+# http://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.
+#
+
+nested.properties
diff --git a/impl/maven-classworlds/src/test/test-data/optionally-existent.conf b/impl/maven-classworlds/src/test/test-data/optionally-existent.conf
new file mode 100644
index 0000000000..fe102574c9
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/optionally-existent.conf
@@ -0,0 +1,14 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from opt
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[opt]
+ optionally ${basedir}/target/test-lib/jakarta.xml.bind-api-4.0.2.jar
+
diff --git a/impl/maven-classworlds/src/test/test-data/optionally-nonexistent.conf b/impl/maven-classworlds/src/test/test-data/optionally-nonexistent.conf
new file mode 100644
index 0000000000..4048e077e6
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/optionally-nonexistent.conf
@@ -0,0 +1,13 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from opt
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[opt]
+ optionally /non/existant/path/*.jar
diff --git a/impl/maven-classworlds/src/test/test-data/realm-syntax.conf b/impl/maven-classworlds/src/test/test-data/realm-syntax.conf
new file mode 100644
index 0000000000..27b9e73f7d
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/realm-syntax.conf
@@ -0,0 +1,3 @@
+
+[foo
+]
diff --git a/impl/maven-classworlds/src/test/test-data/resources/classworlds.conf b/impl/maven-classworlds/src/test/test-data/resources/classworlds.conf
new file mode 100644
index 0000000000..d60fb1f5eb
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/resources/classworlds.conf
@@ -0,0 +1,12 @@
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is foo from root
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[root]
+load ${basedir}/target/test-jars/resources/werkflow.jar
diff --git a/impl/maven-classworlds/src/test/test-data/set-using-existent.conf b/impl/maven-classworlds/src/test/test-data/set-using-existent.conf
new file mode 100644
index 0000000000..11415b8ebf
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/set-using-existent.conf
@@ -0,0 +1,19 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from opt
+
+# ------------------------------------------------------------
+# Set properties
+# ------------------------------------------------------------
+set set.using.existent using ${basedir}/src/test/test-data/set-using-existent.properties
+set set.using.default using ${basedir}/src/test/test-data/set-using-existent.properties default testSetUsingExistentDefault
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[opt]
+
diff --git a/impl/maven-classworlds/src/test/test-data/set-using-existent.properties b/impl/maven-classworlds/src/test/test-data/set-using-existent.properties
new file mode 100644
index 0000000000..6434aef94a
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/set-using-existent.properties
@@ -0,0 +1,19 @@
+################################################################################
+#
+# Copyright 2001-2006 The Codehaus Foundation.
+#
+# Licensed 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
+#
+# http://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.
+#
+
+set.using.existent=testSetUsingExistent
+
diff --git a/impl/maven-classworlds/src/test/test-data/set-using-missing.conf b/impl/maven-classworlds/src/test/test-data/set-using-missing.conf
new file mode 100644
index 0000000000..6f8ccc53d2
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/set-using-missing.conf
@@ -0,0 +1,19 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from opt
+
+# ------------------------------------------------------------
+# Set properties
+# ------------------------------------------------------------
+set set.using.missing default testSetUsingMissingDefault
+set set.using.filtered.default default ${user.home}/m2
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[opt]
+
diff --git a/impl/maven-classworlds/src/test/test-data/set-using-nonexistent.conf b/impl/maven-classworlds/src/test/test-data/set-using-nonexistent.conf
new file mode 100644
index 0000000000..4640cf52c2
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/set-using-nonexistent.conf
@@ -0,0 +1,19 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from opt
+
+# ------------------------------------------------------------
+# Set properties
+# ------------------------------------------------------------
+set set.using.nonexistent using ${basedir}/src/test/test-data/set-using-nonexistent.properties
+set set.using.nonexistent.default using ${basedir}/src/test/test-data/set-using-nonexistent.properties default testSetUsingNonExistentDefault
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[opt]
+
diff --git a/impl/maven-classworlds/src/test/test-data/unhandled.conf b/impl/maven-classworlds/src/test/test-data/unhandled.conf
new file mode 100644
index 0000000000..3f808fe665
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/unhandled.conf
@@ -0,0 +1,2 @@
+
+foo
diff --git a/impl/maven-classworlds/src/test/test-data/valid-enh-launch-exitCode.conf b/impl/maven-classworlds/src/test/test-data/valid-enh-launch-exitCode.conf
new file mode 100644
index 0000000000..a88204a5ed
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid-enh-launch-exitCode.conf
@@ -0,0 +1,6 @@
+
+main is b.Bb from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
+ load ${basedir}/target/test-jars/b.jar
diff --git a/impl/maven-classworlds/src/test/test-data/valid-enh-launch.conf b/impl/maven-classworlds/src/test/test-data/valid-enh-launch.conf
new file mode 100644
index 0000000000..02bb60a912
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid-enh-launch.conf
@@ -0,0 +1,6 @@
+
+main is b.B from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
+ load ${basedir}/target/test-jars/b.jar
diff --git a/impl/maven-classworlds/src/test/test-data/valid-from-from-from.conf b/impl/maven-classworlds/src/test/test-data/valid-from-from-from.conf
new file mode 100644
index 0000000000..49895b093c
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid-from-from-from.conf
@@ -0,0 +1,24 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is com.from.from.from.Main from from
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[from]
+ load ${basedir}/target/test-jars/from-from-0.0.1-from-load-import.jar
+
+[ant]
+ import com.from.from.from from from
+ load ${basedir}/target/test-lib/ant-1.10.14.jar
+
+[maven]
+ import com.from.from.from from from
+ load ${basedir}/target/test-lib/log4j-api-2.23.1.jar
+
+[glob]
+ load ${basedir}/target/test-jars/*.jar
diff --git a/impl/maven-classworlds/src/test/test-data/valid-launch-exitCode.conf b/impl/maven-classworlds/src/test/test-data/valid-launch-exitCode.conf
new file mode 100644
index 0000000000..20ada89658
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid-launch-exitCode.conf
@@ -0,0 +1,5 @@
+
+main is a.Aa from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
diff --git a/impl/maven-classworlds/src/test/test-data/valid-launch.conf b/impl/maven-classworlds/src/test/test-data/valid-launch.conf
new file mode 100644
index 0000000000..4752d09ca5
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid-launch.conf
@@ -0,0 +1,5 @@
+
+main is a.A from app
+
+[app]
+ load ${basedir}/target/test-jars/a.jar
diff --git a/impl/maven-classworlds/src/test/test-data/valid.conf b/impl/maven-classworlds/src/test/test-data/valid.conf
new file mode 100644
index 0000000000..e85024bf00
--- /dev/null
+++ b/impl/maven-classworlds/src/test/test-data/valid.conf
@@ -0,0 +1,24 @@
+
+# ------------------------------------------------------------
+# Define the main entry-point
+# ------------------------------------------------------------
+
+main is org.apache.maven.app.App from maven
+
+# ------------------------------------------------------------
+# Start defining realms
+# ------------------------------------------------------------
+
+[xml]
+ load ${basedir}/target/test-lib/jakarta.xml.bind-api-4.0.2.jar
+
+[ant]
+ import jakarta.xml.bind from xml
+ load ${basedir}/target/test-lib/ant-1.10.14.jar
+
+[maven]
+ import jakarta.xml.bind from xml
+ load ${basedir}/target/test-lib/log4j-api-2.23.1.jar
+
+[glob]
+ load ${basedir}/target/test-jars/*.jar
diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml
index 06bd4c72dd..6d91bf9da4 100644
--- a/impl/maven-cli/pom.xml
+++ b/impl/maven-cli/pom.xml
@@ -150,8 +150,8 @@ under the License.
</dependency>
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
index f7e5593198..cbb8d25b06 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
@@ -23,6 +23,7 @@
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.Invoker;
@@ -74,8 +75,13 @@ public static int main(String[] args, ClassWorld world) throws IOException {
private static int delegateMain(String mainClass, String[] args, ClassWorld world) throws IOException {
try {
- Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(mainClass);
- Method method = clazz.getMethod("main", String[].class, ClassWorld.class);
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class<?> clazz = cl.loadClass(mainClass);
+ Class<?> cwClass = cl.loadClass(ClassWorld.class.getName());
+ Method method = clazz.getMethod("main", String[].class, cwClass);
+ if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
+ throw new NoSuchMethodException(mainClass + ".main(String[], ClassWorld)");
+ }
return (int) method.invoke(null, args, world);
} catch (ClassNotFoundException e) {
throw new IOException("Cannot find maven.mainClass: " + mainClass, e);
diff --git a/impl/maven-core/pom.xml b/impl/maven-core/pom.xml
index 8a3719d63b..5b76715677 100644
--- a/impl/maven-core/pom.xml
+++ b/impl/maven-core/pom.xml
@@ -173,8 +173,8 @@ under the License.
<!-- Sisu/Eclipse DI -->
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
@@ -485,6 +485,40 @@ under the License.
<exclude>org.apache.maven.toolchain.ToolchainManagerPrivate</exclude>
<exclude>org.apache.maven.toolchain.ToolchainPrivate</exclude>
<exclude>org.apache.maven.toolchain.ToolchainsBuilder</exclude>
+ <!-- ClassRealm API changes for Maven 4 - return type changes from Plexus ClassRealm to Maven ClassRealm -->
+ <exclude>org.apache.maven.classrealm.ClassRealmManager#createExtensionRealm(org.apache.maven.model.Plugin,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.ClassRealmManager#createPluginRealm(org.apache.maven.model.Plugin,java.lang.ClassLoader,java.util.List,java.util.Map,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.ClassRealmManager#createProjectRealm(org.apache.maven.model.Model,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.ClassRealmManager#getCoreRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.ClassRealmManager#getMavenApiRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.ClassRealmManagerDelegate#setupRealm(org.codehaus.plexus.classworlds.realm.ClassRealm,org.apache.maven.classrealm.ClassRealmRequest):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.classrealm.DefaultClassRealmManager#createExtensionRealm(org.apache.maven.model.Plugin,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.DefaultClassRealmManager#createPluginRealm(org.apache.maven.model.Plugin,java.lang.ClassLoader,java.util.List,java.util.Map,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.DefaultClassRealmManager#createProjectRealm(org.apache.maven.model.Model,java.util.List):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.DefaultClassRealmManager#getCoreRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.classrealm.DefaultClassRealmManager#getMavenApiRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.BuildPluginManager#getPluginRealm(org.apache.maven.execution.MavenSession,org.apache.maven.plugin.descriptor.PluginDescriptor):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.DefaultBuildPluginManager#getPluginRealm(org.apache.maven.execution.MavenSession,org.apache.maven.plugin.descriptor.PluginDescriptor):METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.ExtensionRealmCache#put(org.apache.maven.plugin.ExtensionRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,org.apache.maven.project.ExtensionDescriptor,java.util.List):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.ExtensionRealmCache$CacheRecord#getRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#getPluginRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#PluginContainerException(org.apache.maven.model.Plugin,org.codehaus.plexus.classworlds.realm.ClassRealm,java.lang.String,org.codehaus.plexus.configuration.PlexusConfigurationException):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#PluginContainerException(org.apache.maven.plugin.descriptor.MojoDescriptor,org.codehaus.plexus.classworlds.realm.ClassRealm,java.lang.String,java.lang.Throwable):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#PluginContainerException(org.apache.maven.model.Plugin,org.codehaus.plexus.classworlds.realm.ClassRealm,java.lang.String,org.codehaus.plexus.component.repository.exception.ComponentRepositoryException):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#PluginContainerException(org.apache.maven.model.Plugin,org.codehaus.plexus.classworlds.realm.ClassRealm,java.lang.String,java.lang.Throwable):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginContainerException#PluginContainerException(org.apache.maven.plugin.descriptor.MojoDescriptor,org.codehaus.plexus.classworlds.realm.ClassRealm,java.lang.String,org.codehaus.plexus.component.repository.exception.ComponentLookupException):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginManagerException#PluginManagerException(org.apache.maven.plugin.descriptor.MojoDescriptor,org.apache.maven.project.MavenProject,java.lang.String,org.codehaus.plexus.classworlds.realm.NoSuchRealmException):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginRealmCache#put(org.apache.maven.plugin.PluginRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,java.util.List):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.PluginRealmCache$CacheRecord#getRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.plugin.PluginRealmCache$CacheRecord#PluginRealmCache$CacheRecord(org.codehaus.plexus.classworlds.realm.ClassRealm,java.util.List):CONSTRUCTOR_REMOVED</exclude>
+ <exclude>org.apache.maven.project.MavenProject#getClassRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <exclude>org.apache.maven.project.MavenProject#setClassRealm(org.codehaus.plexus.classworlds.realm.ClassRealm):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.project.ProjectRealmCache#put(org.apache.maven.project.ProjectRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,org.eclipse.aether.graph.DependencyFilter):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.project.ProjectRealmCache$CacheRecord#getRealm():METHOD_RETURN_TYPE_CHANGED</exclude>
+ <!-- Additional cache-related method removals -->
+ <exclude>org.apache.maven.plugin.DefaultExtensionRealmCache#put(org.apache.maven.plugin.ExtensionRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,org.apache.maven.project.ExtensionDescriptor,java.util.List):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.plugin.DefaultPluginRealmCache#put(org.apache.maven.plugin.PluginRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,java.util.List):METHOD_REMOVED</exclude>
+ <exclude>org.apache.maven.project.DefaultProjectRealmCache#put(org.apache.maven.project.ProjectRealmCache$Key,org.codehaus.plexus.classworlds.realm.ClassRealm,org.eclipse.aether.graph.DependencyFilter):METHOD_REMOVED</exclude>
</excludes>
</parameter>
</configuration>
diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
index e25472ec92..d1ac674a7e 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
@@ -46,6 +46,7 @@
import org.apache.maven.project.ProjectDependenciesResolver;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.project.artifact.ProjectArtifactsCache;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
@@ -112,9 +113,12 @@ public void resolveProjectDependencies(
throws LifecycleExecutionException {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try {
- ClassLoader projectRealm = project.getClassRealm();
- if (projectRealm != null && projectRealm != tccl) {
- Thread.currentThread().setContextClassLoader(projectRealm);
+ ClassRealm projectRealm = project.getClassRealm();
+ if (projectRealm != null) {
+ ClassLoader projectClassLoader = projectRealm;
+ if (projectClassLoader != tccl) {
+ Thread.currentThread().setContextClassLoader(projectClassLoader);
+ }
}
if (project.getDependencyArtifacts() == null) {
diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
index d881c31b38..124607c148 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
@@ -430,6 +430,7 @@ private void createPluginRealm(
pluginArtifacts = toMavenArtifacts(result);
+ // Classic classpath-based plugin
pluginRealm = classRealmManager.createPluginRealm(
plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
@@ -488,7 +489,7 @@ private Map<String, ClassLoader> calcImports(
MavenProject project, ClassLoader parent, List<String> imports, boolean v4api) {
Map<String, ClassLoader> foreignImports = new HashMap<>();
- ClassLoader projectRealm = project.getClassRealm();
+ ClassRealm projectRealm = project.getClassRealm();
if (projectRealm != null) {
foreignImports.put("", projectRealm);
} else {
@@ -566,6 +567,7 @@ private <T> T loadV4Mojo(
org.apache.maven.api.plugin.Log log = new DefaultLog(
LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getImplementation()));
try {
+ // Discover components from the effective ClassLoader (module layer loader for modular plugins)
Injector injector = Injector.create();
injector.discover(pluginRealm);
// Add known classes
@@ -579,7 +581,7 @@ private <T> T loadV4Mojo(
services.forEach((itf, svc) -> injector.bindSupplier((Class<Service>) itf, (Supplier<Service>) svc));
mojo = mojoInterface.cast(injector.getInstance(
- Key.of(mojoDescriptor.getImplementationClass(), mojoDescriptor.getRoleHint())));
+ Key.of(pluginRealm.loadClass(mojoDescriptor.getImplementation()), mojoDescriptor.getRoleHint())));
} catch (Exception e) {
throw new PluginContainerException(mojoDescriptor, pluginRealm, "Unable to lookup Mojo", e);
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java
index 724a0c88b7..131dec8aed 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java
@@ -279,13 +279,16 @@ private void disposeRealm(ClassRealm realm) {
@Override
public void selectProjectRealm(MavenProject project) {
- ClassLoader projectRealm = project.getClassRealm();
+ ClassRealm projectRealm = project.getClassRealm();
+ ClassLoader classLoader;
if (projectRealm == null) {
- projectRealm = classRealmManager.getCoreRealm();
+ classLoader = classRealmManager.getCoreRealm();
+ } else {
+ classLoader = projectRealm;
}
- Thread.currentThread().setContextClassLoader(projectRealm);
+ Thread.currentThread().setContextClassLoader(classLoader);
}
private static boolean containsExpression(String value) {
diff --git a/impl/maven-core/src/main/resources/META-INF/maven/extension.xml b/impl/maven-core/src/main/resources/META-INF/maven/extension.xml
index 56667ed69e..570fece8f4 100644
--- a/impl/maven-core/src/main/resources/META-INF/maven/extension.xml
+++ b/impl/maven-core/src/main/resources/META-INF/maven/extension.xml
@@ -77,12 +77,9 @@ under the License.
<exportedPackage>org.eclipse.aether.version</exportedPackage>
<exportedPackage>org.eclipse.aether.util</exportedPackage>
- <!-- plexus-classworlds -->
+ <!-- maven-classworlds -->
<exportedPackage>org.codehaus.plexus.classworlds</exportedPackage>
- <!-- classworlds (for legacy code) -->
- <exportedPackage>org.codehaus.classworlds</exportedPackage>
-
<!-- plexus-utils (for DOM-type fields in maven-model) -->
<exportedPackage>org.codehaus.plexus.util.xml.Xpp3Dom</exportedPackage>
<exportedPackage>org.codehaus.plexus.util.xml.Xpp3DomBuilder</exportedPackage>
@@ -154,7 +151,7 @@ under the License.
<exportedArtifact>org.apache.maven:maven-api-xml</exportedArtifact>
<exportedArtifact>classworlds:classworlds</exportedArtifact>
- <exportedArtifact>org.codehaus.plexus:plexus-classworlds</exportedArtifact>
+ <exportedArtifact>org.apache.maven:maven-classworlds</exportedArtifact>
<exportedArtifact>org.codehaus.plexus:plexus-component-api</exportedArtifact>
<exportedArtifact>org.codehaus.plexus:plexus-container-default</exportedArtifact>
<exportedArtifact>plexus:plexus-container-default</exportedArtifact>
diff --git a/impl/maven-core/src/test/java/org/apache/maven/classrealm/DefaultClassRealmManagerTest.java b/impl/maven-core/src/test/java/org/apache/maven/classrealm/DefaultClassRealmManagerTest.java
index 411a483b22..d604c1cb60 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/classrealm/DefaultClassRealmManagerTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/classrealm/DefaultClassRealmManagerTest.java
@@ -29,6 +29,7 @@
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.PlexusContainerException;
+import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.eclipse.aether.artifact.Artifact;
import org.junit.jupiter.api.Test;
@@ -58,7 +59,8 @@ private DefaultClassRealmManager newDefaultClassRealmManager(PlexusContainer con
return new DefaultClassRealmManager(
new DefaultCoreRealm(container),
new ArrayList<ClassRealmManagerDelegate>(),
- new CoreExports(new ClassRealm(null, "test", null), new HashSet<String>(), exportedPackages));
+ new CoreExports(
+ new ClassRealm(new ClassWorld(), "test", null), new HashSet<String>(), exportedPackages));
}
private List<Artifact> newTestArtifactList() {
diff --git a/impl/pom.xml b/impl/pom.xml
index b41717c555..d05a9ce287 100644
--- a/impl/pom.xml
+++ b/impl/pom.xml
@@ -31,6 +31,7 @@ under the License.
<name>Maven 4 Implementation Modules</name>
<modules>
+ <module>maven-classworlds</module>
<module>maven-support</module>
<module>maven-impl</module>
<module>maven-di</module>
diff --git a/its/core-it-suite/src/test/resources/mng-5530-mojo-execution-scope/extension/src/main/java/org/apache/maven/its/mng5530/mojoexecutionscope/extension/TestClassRealmManagerDelegate.java b/its/core-it-suite/src/test/resources/mng-5530-mojo-execution-scope/extension/src/main/java/org/apache/maven/its/mng5530/mojoexecutionscope/extension/TestClassRealmManagerDelegate.java
index 6c064d2731..805e9a8ba6 100644
--- a/its/core-it-suite/src/test/resources/mng-5530-mojo-execution-scope/extension/src/main/java/org/apache/maven/its/mng5530/mojoexecutionscope/extension/TestClassRealmManagerDelegate.java
+++ b/its/core-it-suite/src/test/resources/mng-5530-mojo-execution-scope/extension/src/main/java/org/apache/maven/its/mng5530/mojoexecutionscope/extension/TestClassRealmManagerDelegate.java
@@ -35,10 +35,10 @@
import javax.inject.Named;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.classrealm.ClassRealmManagerDelegate;
import org.apache.maven.classrealm.ClassRealmRequest;
import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
-import org.codehaus.plexus.classworlds.realm.ClassRealm;
@Named
public class TestClassRealmManagerDelegate implements ClassRealmManagerDelegate {
diff --git a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java b/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
index edb262ce2d..287c936c7e 100644
--- a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
+++ b/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
@@ -18,9 +18,9 @@
*/
package org.apache.maven.its.core_extensions;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.classrealm.ClassRealmManagerDelegate;
import org.apache.maven.classrealm.ClassRealmRequest;
-import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.annotations.Component;
@Component(role = ClassRealmManagerDelegate.class, hint = "TestClassRealmManagerDelegate")
diff --git a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java b/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
index edb262ce2d..287c936c7e 100644
--- a/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
+++ b/its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java
@@ -18,9 +18,9 @@
*/
package org.apache.maven.its.core_extensions;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.classrealm.ClassRealmManagerDelegate;
import org.apache.maven.classrealm.ClassRealmRequest;
-import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.annotations.Component;
@Component(role = ClassRealmManagerDelegate.class, hint = "TestClassRealmManagerDelegate")
diff --git a/pom.xml b/pom.xml
index 3a0c0f2eda..5e38f93636 100644
--- a/pom.xml
+++ b/pom.xml
@@ -144,7 +144,6 @@ under the License.
<!-- various versions -->
<asmVersion>9.11.1</asmVersion>
<byteBuddyVersion>1.18.15</byteBuddyVersion>
- <classWorldsVersion>2.12.1</classWorldsVersion>
<commonsCliVersion>1.11.0</commonsCliVersion>
<domtripVersion>1.8.0</domtripVersion>
<guiceVersion>5.1.0</guiceVersion>
@@ -400,6 +399,10 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-xml-impl</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-classworlds</artifactId>
+ </exclusion>
</exclusions>
</dependency>
<dependency>
@@ -423,9 +426,9 @@ under the License.
<version>1</version>
</dependency>
<dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-classworlds</artifactId>
- <version>${classWorldsVersion}</version>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-classworlds</artifactId>
+ <version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
@@ -648,6 +651,10 @@ under the License.
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-classworlds</artifactId>
+ </exclusion>
</exclusions>
</dependency>
<dependency>
@@ -1364,7 +1371,7 @@ under the License.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
- <version>2.23.0</version>
+ <version>2.22.0</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
<allowMajorUpdates>false</allowMajorUpdates>
diff --git a/src/graph/ReactorGraph.java b/src/graph/ReactorGraph.java
index 4dc29fe734..162f14d603 100755
--- a/src/graph/ReactorGraph.java
+++ b/src/graph/ReactorGraph.java
@@ -42,7 +42,7 @@ public class ReactorGraph {
CLUSTER_PATTERNS.put("JLine", Pattern.compile("^org\\.jline:.*"));
CLUSTER_PATTERNS.put("Maven 4 API", Pattern.compile("^org\\.apache\\.maven:maven-api-(?!impl).*"));
CLUSTER_PATTERNS.put("Maven Resolver 2", Pattern.compile("^org\\.apache\\.maven\\.resolver:.*"));
- CLUSTER_PATTERNS.put("Maven 4 Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(support|impl|di|core|cli|xml|jline|logging|executor|testing):.*"));
+ CLUSTER_PATTERNS.put("Maven 4 Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(support|impl|di|core|cli|xml|jline|logging|executor|testing|classworlds):.*"));
CLUSTER_PATTERNS.put("Maven 3 Compatibility", Pattern.compile("^org\\.apache\\.maven:maven-(artifact|builder-support|compat|embedder|model|model-builder|plugin-api|repository-metadata|resolver-provider|settings|settings-builder|toolchain-builder|toolchain-model):.*"));
CLUSTER_PATTERNS.put("Sisu", Pattern.compile("(^org\\.eclipse\\.sisu:.*)|(.*:guice:.*)|(.*:javax.inject:.*)|(.*:javax.annotation-api:.*)|(.*:aopalliance:.*)"));
CLUSTER_PATTERNS.put("Plexus", Pattern.compile("^org\\.codehaus\\.plexus:.*"));