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");