diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 6332348c3..6a833d0fb 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -8,6 +8,7 @@ package dan200.computercraft; import dan200.computercraft.api.turtle.event.TurtleAction; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRule; +import dan200.computercraft.core.asm.GenericSource; import dan200.computercraft.shared.Config; import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.computer.core.ClientComputerRegistry; @@ -16,6 +17,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; import dan200.computercraft.shared.pocket.peripherals.PocketModem; import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; import dan200.computercraft.shared.turtle.upgrades.*; +import dan200.computercraft.shared.util.ServiceUtil; import net.minecraftforge.fml.common.Mod; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -133,6 +135,6 @@ public final class ComputerCraft { Config.setup(); Registry.setup(); + GenericSource.setup( () -> ServiceUtil.loadServicesForge( GenericSource.class ) ); } - } diff --git a/src/main/java/dan200/computercraft/core/asm/GenericSource.java b/src/main/java/dan200/computercraft/core/asm/GenericSource.java index 77c4a0ca3..71cab0b26 100644 --- a/src/main/java/dan200/computercraft/core/asm/GenericSource.java +++ b/src/main/java/dan200/computercraft/core/asm/GenericSource.java @@ -9,6 +9,7 @@ package dan200.computercraft.core.asm; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; +import dan200.computercraft.shared.util.ServiceUtil; import net.minecraft.util.ResourceLocation; import javax.annotation.Nonnull; @@ -16,11 +17,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.ServiceLoader; +import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +import java.util.stream.Stream; /** * A generic source of {@link LuaMethod} functions. This allows for injecting methods onto objects you do not own. @@ -42,6 +44,18 @@ public interface GenericSource @Nonnull ResourceLocation id(); + /** + * Register a stream of generic sources. + * + * @param sources The source of generic methods. + * @see ServiceUtil For ways to load this. Sadly {@link java.util.ServiceLoader} is broken under Forge, but we don't + * want to add a hard-dep on Forge within core either. + */ + static void setup( Supplier> sources ) + { + GenericMethod.sources = sources; + } + /** * A generic method is a method belonging to a {@link GenericSource} with a known target. */ @@ -51,6 +65,7 @@ public interface GenericSource final LuaFunction annotation; final Class target; + static Supplier> sources; private static List cache; GenericMethod( Method method, LuaFunction annotation, Class target ) @@ -68,10 +83,16 @@ public interface GenericSource static List all() { if( cache != null ) return cache; - return cache = StreamSupport - .stream( ServiceLoader.load( GenericSource.class, GenericSource.class.getClassLoader() ).spliterator(), false ) + if( sources == null ) + { + ComputerCraft.log.warn( "Getting GenericMethods without a provider" ); + return cache = Collections.emptyList(); + } + + return cache = sources.get() .flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) ) - .map( method -> { + .map( method -> + { LuaFunction annotation = method.getAnnotation( LuaFunction.class ); if( annotation == null ) return null; diff --git a/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java b/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java new file mode 100644 index 000000000..6ff2d898e --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java @@ -0,0 +1,63 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.shared.util; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import net.minecraftforge.fml.ModList; +import org.objectweb.asm.Type; + +import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public final class ServiceUtil +{ + private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" ); + + private ServiceUtil() + { + } + + public static Stream loadServices( Class target ) + { + return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false ); + } + + public static Stream loadServicesForge( Class target ) + { + Type type = Type.getType( target ); + ClassLoader loader = ComputerCraftAPI.class.getClassLoader(); + return ModList.get().getAllScanData().stream() + .flatMap( x -> x.getAnnotations().stream() ) + .filter( x -> x.getAnnotationType().equals( AUTO_SERVICE ) ) + .filter( x -> { + Object value = x.getAnnotationData().get( "value" ); + return value instanceof List && ((List) value).contains( type ); + } ) + .flatMap( x -> { + try + { + Class klass = loader.loadClass( x.getClassType().getClassName() ); + if( !target.isAssignableFrom( klass ) ) + { + ComputerCraft.log.error( "{} is not a subtype of {}", x.getClassType().getClassName(), target.getName() ); + return Stream.empty(); + } + + Class casted = klass.asSubclass( target ); + return Stream.of( casted.newInstance() ); + } + catch( ReflectiveOperationException e ) + { + ComputerCraft.log.error( "Cannot load {}", x.getClassType(), e ); + return Stream.empty(); + } + } ); + } +}