Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* 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.
* <p>
* This is not an overload of {@link #newRealm(String, ClassLoader)} because a second two argument
* <code>newRealm</code> would make the existing <code>newRealm(id, null)</code> calls ambiguous.
*
* @param id The identifier for this realm, must not be <code>null</code>.
* @param factory the factory building the realm for that id, must not be <code>null</code> and must not
* return <code>null</code>
* @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<String, ClassRealm> 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) {
Expand Down
73 changes: 73 additions & 0 deletions src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String, ClassRealm>) 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");
Expand Down
Loading