Skip to content

Commit

Permalink
Use FML's scan data to gather annotations
Browse files Browse the repository at this point in the history
We can just scrape them from the @autoservice annotation, which saves us
having to duplicate any work. Hopefully fixes #501, but I haven't tested
in a non-dev environment yet.
  • Loading branch information
SquidDev committed Jul 23, 2020
1 parent fb70a1a commit d51851e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 6 deletions.
4 changes: 3 additions & 1 deletion src/main/java/dan200/computercraft/ComputerCraft.java
Expand Up @@ -8,6 +8,7 @@
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;
Expand All @@ -16,6 +17,7 @@
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;
Expand Down Expand Up @@ -133,6 +135,6 @@ public ComputerCraft()
{
Config.setup();
Registry.setup();
GenericSource.setup( () -> ServiceUtil.loadServicesForge( GenericSource.class ) );
}

}
31 changes: 26 additions & 5 deletions src/main/java/dan200/computercraft/core/asm/GenericSource.java
Expand Up @@ -9,18 +9,20 @@
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;
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.
Expand All @@ -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<Stream<GenericSource>> sources )
{
GenericMethod.sources = sources;
}

/**
* A generic method is a method belonging to a {@link GenericSource} with a known target.
*/
Expand All @@ -51,6 +65,7 @@ class GenericMethod
final LuaFunction annotation;
final Class<?> target;

static Supplier<Stream<GenericSource>> sources;
private static List<GenericMethod> cache;

GenericMethod( Method method, LuaFunction annotation, Class<?> target )
Expand All @@ -68,10 +83,16 @@ class GenericMethod
static List<GenericMethod> 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;

Expand Down
63 changes: 63 additions & 0 deletions 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 <T> Stream<T> loadServices( Class<T> target )
{
return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false );
}

public static <T> Stream<T> loadServicesForge( Class<T> 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<? extends T> casted = klass.asSubclass( target );
return Stream.of( casted.newInstance() );
}
catch( ReflectiveOperationException e )
{
ComputerCraft.log.error( "Cannot load {}", x.getClassType(), e );
return Stream.empty();
}
} );
}
}

0 comments on commit d51851e

Please sign in to comment.