Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better typing for PluginLoader#loadPlugin(..) #3242

Merged
merged 1 commit into from Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -45,20 +45,20 @@ class PluginLoader {
/**
* Scans the classpath for given pluginType. If not found, default class is used.
*/
@SuppressWarnings("unchecked")
<T> T loadPlugin(final Class<T> pluginType) {
return (T) loadPlugin(pluginType, null);
return loadPlugin(pluginType, null);
}

/**
* Scans the classpath for given {@code preferredPluginType}. If not found scan for {@code
* alternatePluginType}. If neither a preferred or alternate plugin is found, default to default
* class of {@code preferredPluginType}.
*
* @return An object of either {@code preferredPluginType} or {@code alternatePluginType}
* @return An object of either {@code preferredPluginType} or {@code alternatePluginType},
* cast to the lowest common denominator in the chain of inheritance
*/
@SuppressWarnings("unchecked")
<PreferredT, AlternateType> Object loadPlugin(
<ReturnT, PreferredT extends ReturnT, AlternateType extends ReturnT> ReturnT loadPlugin(
final Class<PreferredT> preferredPluginType,
final Class<AlternateType> alternatePluginType) {
try {
Expand All @@ -74,22 +74,23 @@ <PreferredT, AlternateType> Object loadPlugin(

return plugins.getDefaultPlugin(preferredPluginType);
} catch (final Throwable t) {
return Proxy.newProxyInstance(
preferredPluginType.getClassLoader(),
new Class<?>[] {preferredPluginType},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
throw new IllegalStateException(
"Could not initialize plugin: "
+ preferredPluginType
+ " (alternate: "
+ alternatePluginType
+ ")",
t);
}
});
return (ReturnT)
Proxy.newProxyInstance(
preferredPluginType.getClassLoader(),
new Class<?>[] {preferredPluginType},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
throw new IllegalStateException(
"Could not initialize plugin: "
+ preferredPluginType
+ " (alternate: "
+ alternatePluginType
+ ")",
t);
}
});
}
}

Expand Down
Expand Up @@ -49,9 +49,8 @@ class PluginRegistry {
new PluginLoader(pluginSwitch).loadPlugins(MockResolver.class);

private final DoNotMockEnforcerWithType doNotMockEnforcer =
(DoNotMockEnforcerWithType)
(new PluginLoader(pluginSwitch)
.loadPlugin(DoNotMockEnforcerWithType.class, DoNotMockEnforcer.class));
new PluginLoader(pluginSwitch)
.loadPlugin(DoNotMockEnforcerWithType.class, DoNotMockEnforcer.class);

PluginRegistry() {
instantiatorProvider =
Expand Down
Expand Up @@ -4,9 +4,10 @@
*/
package org.mockito.internal.configuration.plugins;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.assertj.core.api.Assertions;
Expand All @@ -29,13 +30,27 @@ public class PluginLoaderTest {

@Test
public void loads_plugin() {
when(initializer.loadImpl(FooPlugin.class)).thenReturn(new FooPlugin());
FooPlugin expected = new FooPlugin();
when(initializer.loadImpl(FooPlugin.class)).thenReturn(expected);

// when
FooPlugin plugin = loader.loadPlugin(FooPlugin.class);

// then
assertNotNull(plugin);
assertSame(expected, plugin);
}

@Test
public void loads_preferred_plugin() {
FooPlugin expected = new FooPlugin();
willReturn(expected).given(initializer).loadImpl(FooPlugin.class);

// when
Object plugin = loader.loadPlugin(FooPlugin.class, BarPlugin.class);

// then
assertSame(expected, plugin);
verify(initializer, never()).loadImpl(BarPlugin.class);
}

@Test
Expand All @@ -48,7 +63,7 @@ public void loads_alternative_plugin() {
Object plugin = loader.loadPlugin(FooPlugin.class, BarPlugin.class);

// then
assertSame(plugin, expected);
assertSame(expected, plugin);
}

@Test
Expand All @@ -62,7 +77,7 @@ public void loads_default_plugin() {
Object plugin = loader.loadPlugin(FooPlugin.class, BarPlugin.class);

// then
assertSame(plugin, expected);
assertSame(expected, plugin);
}

@Test
Expand All @@ -87,9 +102,141 @@ public void call() throws Throwable {
.hasCause(cause);
}

@Test
public void loads_preferred_plugin_inheritance() {
FooChildPlugin expected = new FooChildPlugin();
willReturn(expected).given(initializer).loadImpl(Foo.class);

// when
Foo plugin = loader.loadPlugin(Foo.class, FooChildPlugin.class);

// then
assertSame(expected, plugin);
verify(initializer, never()).loadImpl(FooChildPlugin.class);
}

@Test
public void loads_alternative_plugin_inheritance() {
willReturn(null).given(initializer).loadImpl(Bar.class);
BarChildPlugin expected = new BarChildPlugin();
willReturn(expected).given(initializer).loadImpl(BarChildPlugin.class);

// when
Bar plugin = loader.loadPlugin(Bar.class, BarChildPlugin.class);

// then
assertSame(expected, plugin);
}

@Test
public void loads_default_plugin_inheritance() {
willReturn(null).given(initializer).loadImpl(Foo.class);
willReturn(null).given(initializer).loadImpl(FooChildPlugin.class);
FooChildPlugin expected = new FooChildPlugin();
willReturn(expected).given(plugins).getDefaultPlugin(Foo.class);

// when
Foo plugin = loader.loadPlugin(Foo.class, FooChildPlugin.class);

// then
assertSame(expected, plugin);
}

@Test
public void loads_preferred_plugin_inheritance_reversed() {
FooChildPlugin expected = new FooChildPlugin();
willReturn(expected).given(initializer).loadImpl(FooChildPlugin.class);

// when
Foo plugin = loader.loadPlugin(FooChildPlugin.class, Foo.class);

// then
assertSame(expected, plugin);
verify(initializer, never()).loadImpl(Foo.class);
}

@Test
public void loads_alternative_plugin_inheritance_reversed() {
willReturn(null).given(initializer).loadImpl(BarChildPlugin.class);
BarChildPlugin expected = new BarChildPlugin();
willReturn(expected).given(initializer).loadImpl(Bar.class);

// when
Bar plugin = loader.loadPlugin(BarChildPlugin.class, Bar.class);

// then
assertSame(expected, plugin);
}

@Test
public void loads_default_plugin_inheritance_reversed() {
willReturn(null).given(initializer).loadImpl(Foo.class);
willReturn(null).given(initializer).loadImpl(FooChildPlugin.class);
FooChildPlugin expected = new FooChildPlugin();
willReturn(expected).given(plugins).getDefaultPlugin(FooChildPlugin.class);

// when
Foo plugin = loader.loadPlugin(FooChildPlugin.class, Foo.class);

// then
assertSame(expected, plugin);
}

@Test
public void loads_preferred_plugin_inheritance_lowest_common_denominator() {
FooBarChildPlugin1 expected = new FooBarChildPlugin1();
willReturn(expected).given(initializer).loadImpl(FooBarChildPlugin1.class);

// when
FooBar plugin = loader.loadPlugin(FooBarChildPlugin1.class, FooBarChildPlugin2.class);

// then
assertSame(expected, plugin);
verify(initializer, never()).loadImpl(FooBarChildPlugin2.class);
}

@Test
public void loads_alternative_plugin_inheritance_lowest_common_denominator() {
willReturn(null).given(initializer).loadImpl(FooBarChildPlugin1.class);
FooBarChildPlugin2 expected = new FooBarChildPlugin2();
willReturn(expected).given(initializer).loadImpl(FooBarChildPlugin2.class);

// when
FooBar plugin = loader.loadPlugin(FooBarChildPlugin1.class, FooBarChildPlugin2.class);

// then
assertSame(expected, plugin);
}

@Test
public void loads_default_plugin_inheritance_lowest_common_denominator() {
willReturn(null).given(initializer).loadImpl(FooBarChildPlugin1.class);
willReturn(null).given(initializer).loadImpl(FooBarChildPlugin2.class);
FooBarChildPlugin1 expected = new FooBarChildPlugin1();
willReturn(expected).given(plugins).getDefaultPlugin(FooBarChildPlugin1.class);

// when
FooBar plugin = loader.loadPlugin(FooBarChildPlugin1.class, FooBarChildPlugin2.class);

// then
assertSame(expected, plugin);
}

static class FooPlugin {}

static class BarPlugin {}

static interface Foo {}
interface Foo {}

interface Bar {}

static class BarChildPlugin implements Bar {}

static class FooChildPlugin implements Foo {}

interface FooBar {}

static class FooBarChildPlugin1 implements FooBar {}

static class FooBarChildPlugin2 implements FooBar {}
}