Skip to content

Commit

Permalink
Support indirect dependencies and multibinder
Browse files Browse the repository at this point in the history
  • Loading branch information
aromaa committed Oct 1, 2024
1 parent ad4e31d commit 4f11a19
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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.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<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
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.test.serviceloader;

public interface CombinableTestPluginService {
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,33 @@
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<CombinableTestPluginService> 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");
}

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");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.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);
}
}
}
18 changes: 17 additions & 1 deletion testplugins/src/main/resources/META-INF/sponge_plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -351,7 +367,7 @@
"dependencies": [
{
"id": "childmoduletestplugin",
"version": "11.0.0",
"version": "12.0.0",
"load-order": "AFTER"
}
]
Expand Down

0 comments on commit 4f11a19

Please sign in to comment.