diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
index 55e77fac..cd746b7f 100644
--- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
+++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
@@ -24,6 +24,8 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
import java.util.function.Predicate;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
@@ -88,6 +90,51 @@ public synchronized ClassRealm newRealm(String id, ClassLoader classLoader, Pred
} else {
realm = new FilteredClassRealm(filter, this, id, classLoader);
}
+
+ return register(id, realm);
+ }
+
+ /**
+ * Adds a class realm built by a caller supplied factory, allowing realm implementations that this class does
+ * not know about.
+ *
+ * The factory is handed the id and runs only once that id is known to be free, so a rejected call has no side
+ * effect and no realm has to be closed again. It runs while this world's monitor is held.
+ *
+ * This is not an overload of {@link #newRealm(String, ClassLoader)} because a second two argument
+ * newRealm would make the existing newRealm(id, null) calls ambiguous.
+ *
+ * @param id The identifier for this realm, must not be null.
+ * @param factory the factory building the realm for that id, must not be null and must not
+ * return null
+ * @return the created class realm
+ * @throws DuplicateRealmException in case a realm with the given id does already exist
+ * @throws IllegalArgumentException if the created realm belongs to a different class world or carries a
+ * different id
+ * @since 2.13.0
+ */
+ public synchronized ClassRealm createRealm(String id, Function factory)
+ throws DuplicateRealmException {
+ Objects.requireNonNull(id, "id cannot be null");
+ Objects.requireNonNull(factory, "factory cannot be null");
+
+ if (realms.containsKey(id)) {
+ throw new DuplicateRealmException(this, id);
+ }
+
+ ClassRealm realm = Objects.requireNonNull(factory.apply(id), "factory returned null realm");
+
+ if (realm.getWorld() != this) {
+ throw new IllegalArgumentException("realm " + id + " belongs to a different class world");
+ }
+ if (!id.equals(realm.getId())) {
+ throw new IllegalArgumentException("realm for id " + id + " carries id " + realm.getId());
+ }
+
+ return register(id, realm);
+ }
+
+ private ClassRealm register(String id, ClassRealm realm) {
realms.put(id, realm);
for (ClassWorldListener listener : listeners) {
diff --git a/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java b/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
index c4bc7d1e..2d4b3489 100644
--- a/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
+++ b/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
@@ -19,6 +19,8 @@
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
import java.util.function.Predicate;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
@@ -29,6 +31,8 @@
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.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -178,6 +182,75 @@ void testNewRealmWithNullFilter() throws Exception {
assertEquals("unfiltered", realm.getId());
}
+ @Test
+ void testCreateRealmWithFactory() throws Exception {
+ TestListener listener = new TestListener();
+ world.addListener(listener);
+
+ ClassRealm realm = world.createRealm("custom", id -> new CustomClassRealm(world, id));
+
+ assertInstanceOf(CustomClassRealm.class, realm);
+ assertSame(realm, world.getRealm("custom"));
+ assertEquals(1, listener.realmCreatedCount);
+ }
+
+ @Test
+ void testCreateRealmWithFactoryDuplicateDoesNotRunTheFactory() throws Exception {
+ ClassRealm registered = world.newRealm("custom");
+ registered.addURL(TestUtil.getTestResourceUrl("a.jar"));
+
+ AtomicBoolean invoked = new AtomicBoolean();
+ DuplicateRealmException e = assertThrows(
+ DuplicateRealmException.class,
+ () -> world.createRealm("custom", id -> {
+ invoked.set(true);
+ return new CustomClassRealm(world, id);
+ }));
+
+ assertEquals("custom", e.getId());
+ assertSame(world, e.getWorld());
+ assertFalse(invoked.get(), "the factory must not run for an id that is already taken");
+ assertSame(registered, world.getRealm("custom"));
+ assertNotNull(registered.getResource("a.properties"), "the registered realm must not have been closed");
+ assertNotNull(registered.loadClass("a.A"));
+ }
+
+ @Test
+ void testCreateRealmWithFactoryFromOtherWorld() throws Exception {
+ try (ClassWorld otherWorld = new ClassWorld()) {
+ ClassRealm foreign = otherWorld.newRealm("foreign");
+
+ assertThrows(IllegalArgumentException.class, () -> world.createRealm("foreign", id -> foreign));
+ assertTrue(world.getRealms().isEmpty());
+ assertNotNull(otherWorld.getClassRealm("foreign"), "the foreign realm must be left alone");
+ }
+ }
+
+ @Test
+ void testCreateRealmWithFactoryReturningForeignId() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> world.createRealm("custom", id -> new CustomClassRealm(world, "somethingElse")));
+ assertTrue(world.getRealms().isEmpty());
+ }
+
+ @Test
+ void testCreateRealmWithNullFactory() {
+ assertThrows(
+ NullPointerException.class, () -> world.createRealm("custom", (Function) null));
+ }
+
+ @Test
+ void testCreateRealmWithFactoryReturningNull() {
+ assertThrows(NullPointerException.class, () -> world.createRealm("custom", id -> null));
+ }
+
+ private static class CustomClassRealm extends ClassRealm {
+ CustomClassRealm(ClassWorld world, String id) {
+ super(world, id, null);
+ }
+ }
+
@Test
void testDisposeRealm() throws Exception {
ClassRealm realm = world.newRealm("temp");