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

Support plugin provided guice module #4133

Merged
merged 5 commits into from
Oct 3, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,16 +43,21 @@
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;

ForgePluginContainer(final ModContainer modContainer) {
this.modContainer = modContainer;
}

public void initializeInstance(Injector injector) {
this.injector = injector;
}

public ModContainer getModContainer() {
return this.modContainer;
}
Expand Down Expand Up @@ -92,6 +99,11 @@ public Object instance() {
return this.modContainer.getMod();
}

@Override
public Injector injector() {
return this.injector;
}

private static final Map<ModContainer, ForgePluginContainer> containers = new MapMaker().weakKeys().makeMap();

public static ForgePluginContainer of(final ModContainer modContainer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@
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;
import org.spongepowered.plugin.PluginContainer;

import java.util.Optional;

Expand Down Expand Up @@ -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 = 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);

LOGGER.trace(Logging.LOADING, "Loaded plugin instance {} of type {}", getModId(), this.modClass.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* 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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* 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<Key<?>, Object> combinedKeys;

BindingHelper(final Binder binder) {
this.binder = binder;
this.combinedKeys = new HashMap<>();
}

@SuppressWarnings({"rawtypes", "unchecked"})
void bind() {
for (final Map.Entry<Key<?>, 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.bindFrom(fromInjector, binding);
}
}

@SuppressWarnings("rawtypes")
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()) {
if (!(privateElement instanceof final Binding privateBinding)
|| !privateElements.getExposedKeys().contains(privateBinding.getKey())) {
continue;
}

this.bindFrom(fromInjector, privateBinding);
}

return;
}

this.bind(fromInjector, binding);
}

@SuppressWarnings({"rawtypes", "unchecked"})
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;
if (Set.class.isAssignableFrom(clazz)) {
destinationCollection = this.getBindData(key, () -> new HashSet<>());
} else {
destinationCollection = this.getBindData(key, () -> new ArrayList<>());
}
final Iterable<Object> originalIterable = (Iterable<Object>) fromInjector.getInstance(key);
for (final Object value : originalIterable) {
destinationCollection.add(value);
}
} else {
this.binder.bind(key).toProvider(() -> fromInjector.getInstance(key));
}
}

@SuppressWarnings("unchecked")
private <T> T getBindData(final Key<T> key, final Supplier<T> newValueSupplier) {
return (T) this.combinedKeys.computeIfAbsent(key, $ -> newValueSupplier.get());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,34 @@
*/
package org.spongepowered.common.inject.plugin;

import com.google.inject.AbstractModule;
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 PluginModule extends AbstractModule {
public final class PrivatePluginModule extends PrivateModule {

private final PluginContainer container;
private final Class<?> pluginClass;

public PluginModule(final PluginContainer container, 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());

Expand Down
Loading