From fe720e4d0f454a6a576f16530da4cdf12f9394a6 Mon Sep 17 00:00:00 2001 From: aromaa Date: Wed, 18 Sep 2024 21:42:49 +0300 Subject: [PATCH 1/4] Support plugin provided guice module --- .../launch/plugin/ForgePluginContainer.java | 14 +++- .../launch/plugin/PluginModContainer.java | 8 +- .../inject/SpongePluginInjectorProvider.java | 32 ++++++++ .../common/inject/plugin/PluginModule.java | 75 ++++++++++++++++++- .../serviceloader/ChildModuleTestPlugin.java | 57 ++++++++++++++ .../ChildModuleTestPluginService.java | 33 ++++++++ .../serviceloader/ParentModuleTestPlugin.java | 49 ++++++++++++ .../resources/META-INF/sponge_plugins.json | 22 ++++++ .../launch/plugin/JavaPluginLoader.java | 12 +-- .../plugin/VanillaJavaPluginContainer.java | 18 ++++- 10 files changed, 299 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/inject/SpongePluginInjectorProvider.java create mode 100644 testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java create mode 100644 testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPluginService.java create mode 100644 testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java diff --git a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/ForgePluginContainer.java b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/ForgePluginContainer.java index c71ed3745b5..5d2b4047244 100644 --- a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/ForgePluginContainer.java +++ b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/ForgePluginContainer.java @@ -25,10 +25,12 @@ package org.spongepowered.forge.launch.plugin; import com.google.common.collect.MapMaker; +import com.google.inject.Injector; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.spongepowered.common.inject.SpongePluginInjectorProvider; import org.spongepowered.common.launch.Launch; import org.spongepowered.forge.launch.ForgeLaunch; import org.spongepowered.plugin.PluginContainer; @@ -41,9 +43,10 @@ import java.util.Objects; import java.util.Optional; -public class ForgePluginContainer implements PluginContainer { +public class ForgePluginContainer implements PluginContainer, SpongePluginInjectorProvider { private final ModContainer modContainer; + private Injector injector; private Logger logger; private PluginMetadata pluginMetadata; @@ -51,6 +54,10 @@ public class ForgePluginContainer implements PluginContainer { this.modContainer = modContainer; } + public void initializeInstance(Injector injector) { + this.injector = injector; + } + public ModContainer getModContainer() { return this.modContainer; } @@ -92,6 +99,11 @@ public Object instance() { return this.modContainer.getMod(); } + @Override + public Injector injector() { + return this.injector; + } + private static final Map containers = new MapMaker().weakKeys().makeMap(); public static ForgePluginContainer of(final ModContainer modContainer) { diff --git a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java index 73f92ca0389..e3cd261231d 100644 --- a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java +++ b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java @@ -44,7 +44,6 @@ import org.spongepowered.common.inject.plugin.PluginModule; import org.spongepowered.common.launch.Launch; import org.spongepowered.forge.launch.event.ForgeEventManager; -import org.spongepowered.plugin.PluginContainer; import java.util.Optional; @@ -84,9 +83,10 @@ private void constructPlugin() { try { LOGGER.trace(Logging.LOADING, "Loading plugin instance {} of type {}", getModId(), this.modClass.getName()); - final PluginContainer pluginContainer = ForgePluginContainer.of(this); - final Injector childInjector = Launch.instance().lifecycle().platformInjector().createChildInjector(new PluginModule(pluginContainer, this.modClass)); - this.modInstance = childInjector.getInstance(this.modClass); + final ForgePluginContainer pluginContainer = ForgePluginContainer.of(this); + final Injector pluginInjector = PluginModule.create(pluginContainer, this.modClass, Launch.instance().lifecycle().platformInjector()); + this.modInstance = pluginInjector.getInstance(this.modClass); + pluginContainer.initializeInstance(pluginInjector); ((ForgeEventManager) MinecraftForge.EVENT_BUS).registerListeners(pluginContainer, this.modInstance); LOGGER.trace(Logging.LOADING, "Loaded plugin instance {} of type {}", getModId(), this.modClass.getName()); diff --git a/src/main/java/org/spongepowered/common/inject/SpongePluginInjectorProvider.java b/src/main/java/org/spongepowered/common/inject/SpongePluginInjectorProvider.java new file mode 100644 index 00000000000..0d275ee9e5f --- /dev/null +++ b/src/main/java/org/spongepowered/common/inject/SpongePluginInjectorProvider.java @@ -0,0 +1,32 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.inject; + +import com.google.inject.Injector; + +public interface SpongePluginInjectorProvider { + + Injector injector(); +} diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java index 5c33576d538..57a12f01564 100644 --- a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java +++ b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java @@ -24,22 +24,36 @@ */ package org.spongepowered.common.inject.plugin; -import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.PrivateModule; import com.google.inject.Scopes; +import com.google.inject.spi.Element; +import com.google.inject.spi.ElementSource; +import com.google.inject.spi.ExposedBinding; +import com.google.inject.spi.PrivateElements; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.Sponge; import org.spongepowered.common.inject.InjectionPointProvider; +import org.spongepowered.common.inject.SpongePluginInjectorProvider; import org.spongepowered.common.inject.provider.PluginConfigurationModule; import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.metadata.model.PluginDependency; + +import java.util.ArrayList; /** * A module installed for each plugin. */ -public final class PluginModule extends AbstractModule { +public final class PluginModule extends PrivateModule { private final PluginContainer container; private final Class pluginClass; - public PluginModule(final PluginContainer container, final Class pluginClass) { + private PluginModule(final PluginContainer container, final Class pluginClass) { this.container = container; this.pluginClass = pluginClass; } @@ -47,6 +61,7 @@ public PluginModule(final PluginContainer container, final Class pluginClass) @Override protected void configure() { this.bind(this.pluginClass).in(Scopes.SINGLETON); + this.expose(this.pluginClass); this.install(new InjectionPointProvider()); @@ -55,6 +70,60 @@ protected void configure() { this.bind(System.Logger.class).toProvider(() -> System.getLogger(this.container.logger().getName())).in(Scopes.SINGLETON); this.install(new PluginConfigurationModule()); + + for (final PluginDependency dependency : this.container.metadata().dependencies()) { + if (dependency.loadOrder() != PluginDependency.LoadOrder.AFTER) { + continue; + } + + Sponge.pluginManager().plugin(dependency.id()).ifPresent(dependencyContainer -> { + if (!(dependencyContainer instanceof final SpongePluginInjectorProvider injectorProvider)) { + return; + } + + for (final Binding binding : injectorProvider.injector().getBindings().values()) { + if (!(binding.getSource() instanceof ElementSource)) { + continue; + } + + if (binding instanceof final ExposedBinding exposedBinding) { + final PrivateElements privateElements = exposedBinding.getPrivateElements(); + for (final Element privateElement : privateElements.getElements()) { + if (!(privateElement instanceof final Binding privateBinding) || !privateElements.getExposedKeys().contains(privateBinding.getKey())) { + continue; + } + + this.bind(privateBinding.getKey()).toProvider(() -> injectorProvider.injector().getInstance(privateBinding.getKey())); + } + + continue; + } + + this.bind(binding.getKey()).toProvider(() -> injectorProvider.injector().getInstance(binding.getKey())); + } + }); + } } + public static Injector create(final PluginContainer container, final Class pluginClass, final @Nullable Injector platformInjector) { + final ArrayList modules = new ArrayList<>(2); + modules.add(new PluginModule(container, pluginClass)); + + final @Nullable Object customModule = container.metadata().property("module").orElse(null); + if (customModule != null) { + try { + final Class moduleClass = Class.forName(customModule.toString(), true, pluginClass.getClassLoader()); + final com.google.inject.Module moduleInstance = (com.google.inject.Module) moduleClass.getConstructor().newInstance(); + modules.add(moduleInstance); + } catch (final Exception ex) { + throw new RuntimeException("Failed to instantiate the custom module!", ex); + } + } + + if (platformInjector != null) { + return platformInjector.createChildInjector(modules); + } else { + return Guice.createInjector(modules); + } + } } diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java new file mode 100644 index 00000000000..c2854879b58 --- /dev/null +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java @@ -0,0 +1,57 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.test.serviceloader; + +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +@Plugin("childmoduletestplugin") +public final class ChildModuleTestPlugin implements ChildModuleTestPluginService { + + private final ChildModuleTestPluginService.External external; + + @Inject + public ChildModuleTestPlugin(final ChildModuleTestPluginService.External external) { + this.external = external; + } + + @Override + public External external() { + return this.external; + } + + private static final class ExternalImpl implements ChildModuleTestPluginService.External { + } + + public static final class Module extends AbstractModule { + + @Override + protected void configure() { + this.bind(ChildModuleTestPluginService.class).to(ChildModuleTestPlugin.class); + this.bind(ChildModuleTestPluginService.External.class).toInstance(new ExternalImpl()); + } + } +} diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPluginService.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPluginService.java new file mode 100644 index 00000000000..caed10442ca --- /dev/null +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPluginService.java @@ -0,0 +1,33 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.test.serviceloader; + +public interface ChildModuleTestPluginService { + + External external(); + + interface External { + } +} diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java new file mode 100644 index 00000000000..7e91202bb63 --- /dev/null +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java @@ -0,0 +1,49 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.test.serviceloader; + +import com.google.inject.Inject; +import org.apache.logging.log4j.Logger; +import org.spongepowered.api.Sponge; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +@Plugin("parentmoduletestplugin") +public final class ParentModuleTestPlugin { + + @Inject + public ParentModuleTestPlugin(final Logger logger, final ChildModuleTestPlugin childModulePlugin, final ChildModuleTestPluginService service, final ChildModuleTestPluginService.External external) { + if (Sponge.pluginManager().plugin("childmoduletestplugin").get().instance() != childModulePlugin) { + logger.error("Mismatched instance of the plugin childmoduletestplugin"); + } + + if (childModulePlugin != service) { + logger.error("Mismatched instance of service component from plugin childmoduletestplugin"); + } + + if (childModulePlugin.external() != external) { + logger.error("Mismatched instance of the external component from plugin childmoduletestplugin"); + } + } +} diff --git a/testplugins/src/main/resources/META-INF/sponge_plugins.json b/testplugins/src/main/resources/META-INF/sponge_plugins.json index 011a37a4d89..a24af118cbe 100644 --- a/testplugins/src/main/resources/META-INF/sponge_plugins.json +++ b/testplugins/src/main/resources/META-INF/sponge_plugins.json @@ -333,6 +333,28 @@ "name": "Resource Pack Tests", "entrypoint": "org.spongepowered.test.resourcepack.ResourcePackTest", "description": "Resource Pack Tests" + }, + { + "id": "childmoduletestplugin", + "name": "Child Module Tests", + "entrypoint": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin", + "description": "Child Module Tests", + "properties": { + "module": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin$Module" + } + }, + { + "id": "parentmoduletestplugin", + "name": "Parent Module Tests", + "entrypoint": "org.spongepowered.test.serviceloader.ParentModuleTestPlugin", + "description": "Parent Module Tests", + "dependencies": [ + { + "id": "childmoduletestplugin", + "version": "11.0.0", + "load-order": "AFTER" + } + ] } ] } diff --git a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java index 410635b1304..04d55c12b87 100644 --- a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java +++ b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java @@ -51,15 +51,9 @@ public VanillaJavaPluginContainer loadPlugin(final Environment environment, fina final String mainClass = container.metadata().entrypoint(); final Class pluginClass = Class.forName(mainClass, true, targetClassLoader); - final Injector parentInjector = Launch.instance().lifecycle().platformInjector(); - final Object plugin; - if (parentInjector != null) { - final Injector childInjector = parentInjector.createChildInjector(new PluginModule(container, pluginClass)); - plugin = childInjector.getInstance(pluginClass); - } else { - plugin = pluginClass.getConstructor().newInstance(); - } - container.initializeInstance(plugin); + final Injector pluginInjector = PluginModule.create(container, pluginClass, Launch.instance().lifecycle().platformInjector()); + final Object plugin = pluginInjector.getInstance(pluginClass); + container.initializeInstance(plugin, pluginInjector); return container; } catch (final Exception ex) { throw new InvalidPluginException("An error occurred creating an instance of plugin '" + container.metadata().id() + "'!", ex); diff --git a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/VanillaJavaPluginContainer.java b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/VanillaJavaPluginContainer.java index 7e1471bdfa0..eae5282f041 100644 --- a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/VanillaJavaPluginContainer.java +++ b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/VanillaJavaPluginContainer.java @@ -24,19 +24,29 @@ */ package org.spongepowered.vanilla.launch.plugin; +import com.google.inject.Injector; import org.spongepowered.api.Sponge; +import org.spongepowered.common.inject.SpongePluginInjectorProvider; import org.spongepowered.plugin.PluginCandidate; import org.spongepowered.plugin.builtin.StandardPluginContainer; -public final class VanillaJavaPluginContainer extends StandardPluginContainer { +public final class VanillaJavaPluginContainer extends StandardPluginContainer implements SpongePluginInjectorProvider { + + private Injector injector; public VanillaJavaPluginContainer(final PluginCandidate candidate) { super(candidate); } - @Override - protected void initializeInstance(final Object instance) { - super.initializeInstance(instance); + public void initializeInstance(final Object instance, final Injector injector) { + this.initializeInstance(instance); + this.injector = injector; + Sponge.eventManager().registerListeners(this, instance); } + + @Override + public Injector injector() { + return this.injector; + } } From 57b82f80f65e497d1132b059dc37254dc275fe0d Mon Sep 17 00:00:00 2001 From: aromaa Date: Sun, 29 Sep 2024 00:02:49 +0300 Subject: [PATCH 2/4] Rename the property --- .../org/spongepowered/common/inject/plugin/PluginModule.java | 2 +- testplugins/src/main/resources/META-INF/sponge_plugins.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java index 57a12f01564..e789e3d5534 100644 --- a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java +++ b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java @@ -109,7 +109,7 @@ public static Injector create(final PluginContainer container, final Class pl final ArrayList modules = new ArrayList<>(2); modules.add(new PluginModule(container, pluginClass)); - final @Nullable Object customModule = container.metadata().property("module").orElse(null); + final @Nullable Object customModule = container.metadata().property("guice-module").orElse(null); if (customModule != null) { try { final Class moduleClass = Class.forName(customModule.toString(), true, pluginClass.getClassLoader()); diff --git a/testplugins/src/main/resources/META-INF/sponge_plugins.json b/testplugins/src/main/resources/META-INF/sponge_plugins.json index a24af118cbe..8d40f4496a5 100644 --- a/testplugins/src/main/resources/META-INF/sponge_plugins.json +++ b/testplugins/src/main/resources/META-INF/sponge_plugins.json @@ -340,7 +340,7 @@ "entrypoint": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin", "description": "Child Module Tests", "properties": { - "module": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin$Module" + "guice-module": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin$Module" } }, { From 4f11a196e912ffbd51b1748f9c2637f7b9868c4d Mon Sep 17 00:00:00 2001 From: aromaa Date: Tue, 1 Oct 2024 20:21:35 +0300 Subject: [PATCH 3/4] Support indirect dependencies and multibinder --- .../common/inject/plugin/BindingHelper.java | 121 ++++++++++++++++++ .../common/inject/plugin/PluginModule.java | 44 +++---- .../serviceloader/ChildModuleTestPlugin.java | 4 +- .../CombinableTestPluginService.java | 28 ++++ .../serviceloader/ParentModuleTestPlugin.java | 14 +- .../SecondChildModuleTestPlugin.java | 41 ++++++ .../resources/META-INF/sponge_plugins.json | 18 ++- 7 files changed, 241 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java create mode 100644 testplugins/src/main/java/org/spongepowered/test/serviceloader/CombinableTestPluginService.java create mode 100644 testplugins/src/main/java/org/spongepowered/test/serviceloader/SecondChildModuleTestPlugin.java diff --git a/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java b/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java new file mode 100644 index 00000000000..97bd7089538 --- /dev/null +++ b/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java @@ -0,0 +1,121 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.inject.plugin; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.spi.Element; +import com.google.inject.spi.ElementSource; +import com.google.inject.spi.ExposedBinding; +import com.google.inject.spi.PrivateElements; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +final class BindingHelper { + + private final Binder binder; + private final Map, Object> combinedKeys; + + BindingHelper(final Binder binder) { + this.binder = binder; + this.combinedKeys = new HashMap<>(); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + void bind() { + for (final Map.Entry, Object> key : this.combinedKeys.entrySet()) { + Object value = key.getValue(); + if (value instanceof Set set) { + value = ImmutableSet.copyOf(set); + } else if (value instanceof Iterable iterable) { + value = ImmutableList.copyOf(iterable); + } + this.binder.bind((Key) key.getKey()).toInstance(value); + } + } + + void bindFrom(final Injector fromInjector) { + for (final Binding binding : fromInjector.getBindings().values()) { + if (!(binding.getSource() instanceof ElementSource)) { + continue; + } + + this.bind(fromInjector, binding); + } + } + + @SuppressWarnings("rawtypes") + void bind(final Injector fromInjector, final Binding binding) { + if (binding instanceof final ExposedBinding exposedBinding) { + final PrivateElements privateElements = exposedBinding.getPrivateElements(); + for (final Element privateElement : privateElements.getElements()) { + if (!(privateElement instanceof final Binding privateBinding) + || !privateElements.getExposedKeys().contains(privateBinding.getKey())) { + continue; + } + + this.bind(fromInjector, privateBinding); + } + + return; + } + + this.bind(fromInjector, binding.getKey()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void bind(final Injector fromInjector, final Key key) { + final Class clazz = key.getTypeLiteral().getRawType(); + if (Iterable.class.isAssignableFrom(clazz)) { + final Collection destinationCollection; + if (Set.class.isAssignableFrom(clazz)) { + destinationCollection = this.getBindData(key, () -> new HashSet<>()); + } else { + destinationCollection = this.getBindData(key, () -> new ArrayList<>()); + } + final Iterable originalIterable = (Iterable) fromInjector.getInstance(key); + for (final Object value : originalIterable) { + destinationCollection.add(value); + } + } else { + this.binder.bind(key).toProvider(() -> fromInjector.getInstance(key)); + } + } + + @SuppressWarnings("unchecked") + private T getBindData(final Key key, final Supplier newValueSupplier) { + return (T) this.combinedKeys.computeIfAbsent(key, $ -> newValueSupplier.get()); + } +} diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java index e789e3d5534..4824773f8de 100644 --- a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java +++ b/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java @@ -24,16 +24,11 @@ */ package org.spongepowered.common.inject.plugin; -import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.PrivateModule; import com.google.inject.Scopes; -import com.google.inject.spi.Element; -import com.google.inject.spi.ElementSource; -import com.google.inject.spi.ExposedBinding; -import com.google.inject.spi.PrivateElements; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.Sponge; @@ -71,38 +66,35 @@ protected void configure() { this.install(new PluginConfigurationModule()); + final BindingHelper bindingHelper = new BindingHelper(this.binder()); for (final PluginDependency dependency : this.container.metadata().dependencies()) { if (dependency.loadOrder() != PluginDependency.LoadOrder.AFTER) { continue; } - Sponge.pluginManager().plugin(dependency.id()).ifPresent(dependencyContainer -> { - if (!(dependencyContainer instanceof final SpongePluginInjectorProvider injectorProvider)) { + Sponge.pluginManager().plugin(dependency.id()).ifPresent(p -> { + if (!(p instanceof final SpongePluginInjectorProvider injectorProvider)) { return; } - for (final Binding binding : injectorProvider.injector().getBindings().values()) { - if (!(binding.getSource() instanceof ElementSource)) { - continue; - } - - if (binding instanceof final ExposedBinding exposedBinding) { - final PrivateElements privateElements = exposedBinding.getPrivateElements(); - for (final Element privateElement : privateElements.getElements()) { - if (!(privateElement instanceof final Binding privateBinding) || !privateElements.getExposedKeys().contains(privateBinding.getKey())) { - continue; - } - - this.bind(privateBinding.getKey()).toProvider(() -> injectorProvider.injector().getInstance(privateBinding.getKey())); - } - - continue; - } + bindingHelper.bindFrom(injectorProvider.injector()); + }); + } - this.bind(binding.getKey()).toProvider(() -> injectorProvider.injector().getInstance(binding.getKey())); + //In-direct dependencies + Sponge.pluginManager() + .plugins() + .stream() + .filter(p -> p.metadata().dependency(this.container.metadata().id()).isPresent()) + .forEach(p -> { + if (!(p instanceof final SpongePluginInjectorProvider injectorProvider)) { + return; } + + bindingHelper.bindFrom(injectorProvider.injector()); }); - } + + bindingHelper.bind(); } public static Injector create(final PluginContainer container, final Class pluginClass, final @Nullable Injector platformInjector) { diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java index c2854879b58..617dd065017 100644 --- a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ChildModuleTestPlugin.java @@ -26,10 +26,11 @@ import com.google.inject.AbstractModule; import com.google.inject.Inject; +import com.google.inject.multibindings.Multibinder; import org.spongepowered.plugin.builtin.jvm.Plugin; @Plugin("childmoduletestplugin") -public final class ChildModuleTestPlugin implements ChildModuleTestPluginService { +public final class ChildModuleTestPlugin implements ChildModuleTestPluginService, CombinableTestPluginService { private final ChildModuleTestPluginService.External external; @@ -50,6 +51,7 @@ public static final class Module extends AbstractModule { @Override protected void configure() { + Multibinder.newSetBinder(this.binder(), CombinableTestPluginService.class).addBinding().to(ChildModuleTestPlugin.class); this.bind(ChildModuleTestPluginService.class).to(ChildModuleTestPlugin.class); this.bind(ChildModuleTestPluginService.External.class).toInstance(new ExternalImpl()); } diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/CombinableTestPluginService.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/CombinableTestPluginService.java new file mode 100644 index 00000000000..42d17259acc --- /dev/null +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/CombinableTestPluginService.java @@ -0,0 +1,28 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.test.serviceloader; + +public interface CombinableTestPluginService { +} diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java index 7e91202bb63..53b6da8e425 100644 --- a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java @@ -29,15 +29,23 @@ import org.spongepowered.api.Sponge; import org.spongepowered.plugin.builtin.jvm.Plugin; +import java.util.Set; + @Plugin("parentmoduletestplugin") public final class ParentModuleTestPlugin { @Inject - public ParentModuleTestPlugin(final Logger logger, final ChildModuleTestPlugin childModulePlugin, final ChildModuleTestPluginService service, final ChildModuleTestPluginService.External external) { + public ParentModuleTestPlugin(final Logger logger, final ChildModuleTestPlugin childModulePlugin, final SecondChildModuleTestPlugin secondChildModulePlugin, + final ChildModuleTestPluginService service, final ChildModuleTestPluginService.External external, + final Set combinablePluginServices) { if (Sponge.pluginManager().plugin("childmoduletestplugin").get().instance() != childModulePlugin) { logger.error("Mismatched instance of the plugin childmoduletestplugin"); } + if (Sponge.pluginManager().plugin("secondchildmoduletestplugin").get().instance() != secondChildModulePlugin) { + logger.error("Mismatched instance of the plugin secondChildModuleTestPlugin"); + } + if (childModulePlugin != service) { logger.error("Mismatched instance of service component from plugin childmoduletestplugin"); } @@ -45,5 +53,9 @@ public ParentModuleTestPlugin(final Logger logger, final ChildModuleTestPlugin c if (childModulePlugin.external() != external) { logger.error("Mismatched instance of the external component from plugin childmoduletestplugin"); } + + if (combinablePluginServices.size() != 2 || !combinablePluginServices.contains(childModulePlugin) || !combinablePluginServices.contains(secondChildModulePlugin)) { + logger.error("Mismatched content of combinablePluginServices"); + } } } diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/SecondChildModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/SecondChildModuleTestPlugin.java new file mode 100644 index 00000000000..6c4ea5618bc --- /dev/null +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/SecondChildModuleTestPlugin.java @@ -0,0 +1,41 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.test.serviceloader; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +@Plugin("secondchildmoduletestplugin") +public final class SecondChildModuleTestPlugin implements CombinableTestPluginService { + + public static final class Module extends AbstractModule { + + @Override + protected void configure() { + Multibinder.newSetBinder(this.binder(), CombinableTestPluginService.class).addBinding().to(SecondChildModuleTestPlugin.class); + } + } +} diff --git a/testplugins/src/main/resources/META-INF/sponge_plugins.json b/testplugins/src/main/resources/META-INF/sponge_plugins.json index 8d40f4496a5..2df27362b16 100644 --- a/testplugins/src/main/resources/META-INF/sponge_plugins.json +++ b/testplugins/src/main/resources/META-INF/sponge_plugins.json @@ -343,6 +343,22 @@ "guice-module": "org.spongepowered.test.serviceloader.ChildModuleTestPlugin$Module" } }, + { + "id": "secondchildmoduletestplugin", + "name": "Second Child Module Tests", + "entrypoint": "org.spongepowered.test.serviceloader.SecondChildModuleTestPlugin", + "description": "Second Child Module Tests", + "properties": { + "guice-module": "org.spongepowered.test.serviceloader.SecondChildModuleTestPlugin$Module" + }, + "dependencies": [ + { + "id": "parentmoduletestplugin", + "version": "12.0.0", + "load-order": "BEFORE" + } + ] + }, { "id": "parentmoduletestplugin", "name": "Parent Module Tests", @@ -351,7 +367,7 @@ "dependencies": [ { "id": "childmoduletestplugin", - "version": "11.0.0", + "version": "12.0.0", "load-order": "AFTER" } ] From d69d4d19b410faefe71795ab8e318b686ffd3bbe Mon Sep 17 00:00:00 2001 From: aromaa Date: Thu, 3 Oct 2024 01:52:35 +0300 Subject: [PATCH 4/4] Prioritize plugins own bindings --- .../launch/plugin/PluginModContainer.java | 4 +- .../common/inject/plugin/BindingHelper.java | 11 ++-- .../common/inject/plugin/PluginGuice.java | 56 ++++++++++++++++ .../inject/plugin/PrivatePluginModule.java | 65 +++++++++++++++++++ ...ginModule.java => PublicPluginModule.java} | 57 ++++------------ .../serviceloader/ParentModuleTestPlugin.java | 10 +++ .../resources/META-INF/sponge_plugins.json | 3 + .../launch/plugin/JavaPluginLoader.java | 4 +- 8 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/inject/plugin/PluginGuice.java create mode 100644 src/main/java/org/spongepowered/common/inject/plugin/PrivatePluginModule.java rename src/main/java/org/spongepowered/common/inject/plugin/{PluginModule.java => PublicPluginModule.java} (55%) diff --git a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java index e3cd261231d..ef3ec2ee006 100644 --- a/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java +++ b/forge/src/launch/java/org/spongepowered/forge/launch/plugin/PluginModContainer.java @@ -41,7 +41,7 @@ import net.minecraftforge.forgespi.language.ModFileScanData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.spongepowered.common.inject.plugin.PluginModule; +import org.spongepowered.common.inject.plugin.PluginGuice; import org.spongepowered.common.launch.Launch; import org.spongepowered.forge.launch.event.ForgeEventManager; @@ -84,7 +84,7 @@ private void constructPlugin() { LOGGER.trace(Logging.LOADING, "Loading plugin instance {} of type {}", getModId(), this.modClass.getName()); final ForgePluginContainer pluginContainer = ForgePluginContainer.of(this); - final Injector pluginInjector = PluginModule.create(pluginContainer, this.modClass, Launch.instance().lifecycle().platformInjector()); + final Injector pluginInjector = PluginGuice.create(pluginContainer, this.modClass, Launch.instance().lifecycle().platformInjector()); this.modInstance = pluginInjector.getInstance(this.modClass); pluginContainer.initializeInstance(pluginInjector); ((ForgeEventManager) MinecraftForge.EVENT_BUS).registerListeners(pluginContainer, this.modInstance); diff --git a/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java b/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java index 97bd7089538..0cd123f0f7f 100644 --- a/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java +++ b/src/main/java/org/spongepowered/common/inject/plugin/BindingHelper.java @@ -72,12 +72,12 @@ void bindFrom(final Injector fromInjector) { continue; } - this.bind(fromInjector, binding); + this.bindFrom(fromInjector, binding); } } @SuppressWarnings("rawtypes") - void bind(final Injector fromInjector, final Binding binding) { + void bindFrom(final Injector fromInjector, final Binding binding) { if (binding instanceof final ExposedBinding exposedBinding) { final PrivateElements privateElements = exposedBinding.getPrivateElements(); for (final Element privateElement : privateElements.getElements()) { @@ -86,17 +86,18 @@ void bind(final Injector fromInjector, final Binding binding) { continue; } - this.bind(fromInjector, privateBinding); + this.bindFrom(fromInjector, privateBinding); } return; } - this.bind(fromInjector, binding.getKey()); + this.bind(fromInjector, binding); } @SuppressWarnings({"rawtypes", "unchecked"}) - private void bind(final Injector fromInjector, final Key key) { + private void bind(final Injector fromInjector, final Binding binding) { + final Key key = binding.getKey(); final Class clazz = key.getTypeLiteral().getRawType(); if (Iterable.class.isAssignableFrom(clazz)) { final Collection destinationCollection; diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PluginGuice.java b/src/main/java/org/spongepowered/common/inject/plugin/PluginGuice.java new file mode 100644 index 00000000000..e76152ea9bf --- /dev/null +++ b/src/main/java/org/spongepowered/common/inject/plugin/PluginGuice.java @@ -0,0 +1,56 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.inject.plugin; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.util.Modules; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.plugin.PluginContainer; + +public final class PluginGuice { + + public static Injector create(final PluginContainer container, final Class pluginClass, final @Nullable Injector platformInjector) { + Module module = Modules.combine(new PrivatePluginModule(container, pluginClass), new PublicPluginModule(container)); + + final @Nullable Object customModule = container.metadata().property("guice-module").orElse(null); + if (customModule != null) { + try { + final Class moduleClass = Class.forName(customModule.toString(), true, pluginClass.getClassLoader()); + final Module moduleInstance = (Module) moduleClass.getConstructor().newInstance(); + module = Modules.override(module).with(moduleInstance); + } catch (final Exception ex) { + throw new RuntimeException("Failed to instantiate the custom module!", ex); + } + } + + if (platformInjector != null) { + return platformInjector.createChildInjector(module); + } else { + return Guice.createInjector(module); + } + } +} diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PrivatePluginModule.java b/src/main/java/org/spongepowered/common/inject/plugin/PrivatePluginModule.java new file mode 100644 index 00000000000..4b52f073d9c --- /dev/null +++ b/src/main/java/org/spongepowered/common/inject/plugin/PrivatePluginModule.java @@ -0,0 +1,65 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.inject.plugin; + +import com.google.inject.PrivateModule; +import com.google.inject.Scopes; +import org.apache.logging.log4j.Logger; +import org.spongepowered.common.inject.InjectionPointProvider; +import org.spongepowered.common.inject.provider.PluginConfigurationModule; +import org.spongepowered.plugin.PluginContainer; + + +/** + * A module installed for each plugin. + * + * Contains the values that are not shared for dependencies or + * that require access to the private resources, like the plugin class. + */ +public final class PrivatePluginModule extends PrivateModule { + + private final PluginContainer container; + private final Class pluginClass; + + PrivatePluginModule(final PluginContainer container, final Class pluginClass) { + this.container = container; + this.pluginClass = pluginClass; + } + + @Override + protected void configure() { + this.bind(this.pluginClass).in(Scopes.SINGLETON); + this.expose(this.pluginClass); + + this.install(new InjectionPointProvider()); + + this.bind(PluginContainer.class).toInstance(this.container); + this.bind(Logger.class).toInstance(this.container.logger()); + this.bind(System.Logger.class).toProvider(() -> System.getLogger(this.container.logger().getName())).in(Scopes.SINGLETON); + + this.install(new PluginConfigurationModule()); + } + +} diff --git a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java b/src/main/java/org/spongepowered/common/inject/plugin/PublicPluginModule.java similarity index 55% rename from src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java rename to src/main/java/org/spongepowered/common/inject/plugin/PublicPluginModule.java index 4824773f8de..9676c6b427c 100644 --- a/src/main/java/org/spongepowered/common/inject/plugin/PluginModule.java +++ b/src/main/java/org/spongepowered/common/inject/plugin/PublicPluginModule.java @@ -24,47 +24,28 @@ */ package org.spongepowered.common.inject.plugin; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; -import com.google.inject.PrivateModule; -import com.google.inject.Scopes; -import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; +import com.google.inject.AbstractModule; import org.spongepowered.api.Sponge; -import org.spongepowered.common.inject.InjectionPointProvider; import org.spongepowered.common.inject.SpongePluginInjectorProvider; -import org.spongepowered.common.inject.provider.PluginConfigurationModule; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.metadata.model.PluginDependency; -import java.util.ArrayList; - /** * A module installed for each plugin. + * Contains the values that are publicly visible and shared + * across dependencies. */ -public final class PluginModule extends PrivateModule { +public final class PublicPluginModule extends AbstractModule { private final PluginContainer container; - private final Class pluginClass; - private PluginModule(final PluginContainer container, final Class pluginClass) { + PublicPluginModule(final PluginContainer container) { this.container = container; - this.pluginClass = pluginClass; } @Override protected void configure() { - this.bind(this.pluginClass).in(Scopes.SINGLETON); - this.expose(this.pluginClass); - - this.install(new InjectionPointProvider()); - - this.bind(PluginContainer.class).toInstance(this.container); - this.bind(Logger.class).toInstance(this.container.logger()); - this.bind(System.Logger.class).toProvider(() -> System.getLogger(this.container.logger().getName())).in(Scopes.SINGLETON); - - this.install(new PluginConfigurationModule()); + this.requestStaticInjection(PreserveHelper.class); final BindingHelper bindingHelper = new BindingHelper(this.binder()); for (final PluginDependency dependency : this.container.metadata().dependencies()) { @@ -97,25 +78,11 @@ protected void configure() { bindingHelper.bind(); } - public static Injector create(final PluginContainer container, final Class pluginClass, final @Nullable Injector platformInjector) { - final ArrayList modules = new ArrayList<>(2); - modules.add(new PluginModule(container, pluginClass)); - - final @Nullable Object customModule = container.metadata().property("guice-module").orElse(null); - if (customModule != null) { - try { - final Class moduleClass = Class.forName(customModule.toString(), true, pluginClass.getClassLoader()); - final com.google.inject.Module moduleInstance = (com.google.inject.Module) moduleClass.getConstructor().newInstance(); - modules.add(moduleInstance); - } catch (final Exception ex) { - throw new RuntimeException("Failed to instantiate the custom module!", ex); - } - } - - if (platformInjector != null) { - return platformInjector.createChildInjector(modules); - } else { - return Guice.createInjector(modules); - } + /** + * If no public module has any bindings, Guice will silently promote + * the private module as the "main" one which leads to everything + * being marked as private, including the plugin provided custom module. + */ + private static final class PreserveHelper { } } diff --git a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java index 53b6da8e425..3128270aac4 100644 --- a/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java +++ b/testplugins/src/main/java/org/spongepowered/test/serviceloader/ParentModuleTestPlugin.java @@ -24,7 +24,9 @@ */ package org.spongepowered.test.serviceloader; +import com.google.inject.AbstractModule; import com.google.inject.Inject; +import com.google.inject.multibindings.Multibinder; import org.apache.logging.log4j.Logger; import org.spongepowered.api.Sponge; import org.spongepowered.plugin.builtin.jvm.Plugin; @@ -58,4 +60,12 @@ public ParentModuleTestPlugin(final Logger logger, final ChildModuleTestPlugin c logger.error("Mismatched content of combinablePluginServices"); } } + + public static final class Module extends AbstractModule { + + @Override + protected void configure() { + Multibinder.newSetBinder(this.binder(), CombinableTestPluginService.class); + } + } } diff --git a/testplugins/src/main/resources/META-INF/sponge_plugins.json b/testplugins/src/main/resources/META-INF/sponge_plugins.json index 2df27362b16..df2cd1ffa01 100644 --- a/testplugins/src/main/resources/META-INF/sponge_plugins.json +++ b/testplugins/src/main/resources/META-INF/sponge_plugins.json @@ -364,6 +364,9 @@ "name": "Parent Module Tests", "entrypoint": "org.spongepowered.test.serviceloader.ParentModuleTestPlugin", "description": "Parent Module Tests", + "properties": { + "guice-module": "org.spongepowered.test.serviceloader.ParentModuleTestPlugin$Module" + }, "dependencies": [ { "id": "childmoduletestplugin", diff --git a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java index 04d55c12b87..1a6ebb38132 100644 --- a/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java +++ b/vanilla/src/launch/java/org/spongepowered/vanilla/launch/plugin/JavaPluginLoader.java @@ -27,7 +27,7 @@ import com.google.inject.Injector; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; -import org.spongepowered.common.inject.plugin.PluginModule; +import org.spongepowered.common.inject.plugin.PluginGuice; import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.Environment; import org.spongepowered.plugin.InvalidPluginException; @@ -51,7 +51,7 @@ public VanillaJavaPluginContainer loadPlugin(final Environment environment, fina final String mainClass = container.metadata().entrypoint(); final Class pluginClass = Class.forName(mainClass, true, targetClassLoader); - final Injector pluginInjector = PluginModule.create(container, pluginClass, Launch.instance().lifecycle().platformInjector()); + final Injector pluginInjector = PluginGuice.create(container, pluginClass, Launch.instance().lifecycle().platformInjector()); final Object plugin = pluginInjector.getInstance(pluginClass); container.initializeInstance(plugin, pluginInjector); return container;