diff --git a/src/api/java/appeng/api/AEPlugin.java b/src/api/java/appeng/api/AEAddon.java similarity index 77% rename from src/api/java/appeng/api/AEPlugin.java rename to src/api/java/appeng/api/AEAddon.java index 96186fff7..6722e75ac 100644 --- a/src/api/java/appeng/api/AEPlugin.java +++ b/src/api/java/appeng/api/AEAddon.java @@ -26,12 +26,16 @@ import java.lang.annotation.Target; /** * Use this annotation on a class in your Mod to have it instantiated during the - * initialization phase of Applied Energistics. AE expects your class to have a - * single constructor and can supply certain arguments to your constructor using - * dependency injection. + * initialization phase of Applied Energistics. + * + * The class also needs to implement {@link IAEAddon}. + * + * AE expects your class to have a single constructor without any parameters. + * + * This is the only way to get access to the public {@link IAppEngApi} instance. */ @Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @Documented -public @interface AEPlugin { +public @interface AEAddon { } diff --git a/src/api/java/appeng/api/AEInjectable.java b/src/api/java/appeng/api/AEInjectable.java index 7dded1cbe..014ab148e 100644 --- a/src/api/java/appeng/api/AEInjectable.java +++ b/src/api/java/appeng/api/AEInjectable.java @@ -25,7 +25,7 @@ import java.lang.annotation.Target; /** * Marks interfaces that can be used as injectable constructor arguments for an - * {@link AEPlugin}. + * {@link AEAddon}. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/api/java/appeng/api/IAEAddon.java b/src/api/java/appeng/api/IAEAddon.java new file mode 100644 index 000000000..4a7759370 --- /dev/null +++ b/src/api/java/appeng/api/IAEAddon.java @@ -0,0 +1,50 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 TeamAppliedEnergistics + * + * 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 appeng.api; + +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; + +/** + * Every AE2 addon requiring access to {@link IAppEngApi}, needs to provide at + * least one class implementing this interface. + * + * Further it requires the class to be annotated with {@link AEAddon}. + * + */ +public interface IAEAddon { + + /** + * This gets called once the API is successfully constructed and ready to be + * used. + * + * For now this happens during {@link FMLCommonSetupEvent}. + * + * Each addon is responsible to maintain a reference to {@link IAppEngApi} for + * future use. Otherwise there is no alternative to access it later. + * + * @param api The API instance when ready. + */ + void onAPIAvailable(IAppEngApi api); + +} diff --git a/src/main/java/appeng/core/AddonLoader.java b/src/main/java/appeng/core/AddonLoader.java new file mode 100644 index 000000000..5b1bdd662 --- /dev/null +++ b/src/main/java/appeng/core/AddonLoader.java @@ -0,0 +1,61 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.core; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +import org.objectweb.asm.Type; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.ModFileScanData; +import net.minecraftforge.forgespi.language.ModFileScanData.AnnotationData; + +import appeng.api.AEAddon; +import appeng.api.IAEAddon; +import appeng.api.IAppEngApi; + +/** + * Loads AE addons on startup and provides them with an {@link IAppEngApi} + * instance. + */ +class AddonLoader { + + public static void loadAddons(IAppEngApi api) { + final Type annotationType = Type.getType(AEAddon.class); + final Collection allScanData = ModList.get().getAllScanData(); + + allScanData.stream().map(ModFileScanData::getAnnotations).flatMap(Set::stream) + .filter(a -> Objects.equals(a.getAnnotationType(), annotationType)).map(AnnotationData::getMemberName) + .forEach(className -> { + try { + final Class clazz = Class.forName(className); + final Class instanceClass = clazz.asSubclass(IAEAddon.class); + final IAEAddon instance = instanceClass.newInstance(); + + instance.onAPIAvailable(api); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | LinkageError e) { + AELog.error("Failed to load: %s", className, e); + throw new RuntimeException(e); + } + }); + } +} diff --git a/src/main/java/appeng/core/AppEng.java b/src/main/java/appeng/core/AppEng.java index be2469a96..1c5945134 100644 --- a/src/main/java/appeng/core/AppEng.java +++ b/src/main/java/appeng/core/AppEng.java @@ -71,14 +71,24 @@ import appeng.client.render.cablebus.CableBusModelLoader; import appeng.client.render.cablebus.P2PTunnelFrequencyModel; import appeng.client.render.crafting.CraftingCubeModelLoader; import appeng.client.render.crafting.EncodedPatternModelLoader; -import appeng.client.render.model.*; +import appeng.client.render.model.BiometricCardModel; +import appeng.client.render.model.ColorApplicatorModel; +import appeng.client.render.model.DriveModel; +import appeng.client.render.model.GlassModel; +import appeng.client.render.model.MemoryCardModel; +import appeng.client.render.model.SkyCompassModel; +import appeng.client.render.model.UVLModelLoader; import appeng.client.render.spatial.SpatialPylonModel; import appeng.core.crash.ModCrashEnhancement; import appeng.core.features.registries.PartModels; import appeng.core.stats.AdvancementTriggers; import appeng.core.sync.network.NetworkHandler; import appeng.core.worlddata.WorldData; -import appeng.entity.*; +import appeng.entity.ChargedQuartzEntity; +import appeng.entity.GrowingCrystalEntity; +import appeng.entity.SingularityEntity; +import appeng.entity.TinyTNTPrimedEntity; +import appeng.entity.TinyTNTPrimedRenderer; import appeng.hooks.TickHandler; import appeng.integration.Integrations; import appeng.parts.PartPlacement; @@ -157,6 +167,7 @@ public final class AppEng { registerNetworkHandler(); + AddonLoader.loadAddons(Api.INSTANCE); } @OnlyIn(Dist.CLIENT) @@ -253,10 +264,6 @@ public final class AppEng { // // AELog.info( "Pre Initialization ( ended after " + watch.elapsed( TimeUnit.MILLISECONDS ) + "ms )" ); // -// // Instantiate all Plugins -// List injectables = Lists.newArrayList( -// AEApi.instance() ); -// new PluginLoader().loadPlugins( injectables, event.getAsmData() ); // } private void startService(final String serviceName, final Thread thread) { diff --git a/src/main/java/appeng/core/PluginLoader.java b/src/main/java/appeng/core/PluginLoader.java deleted file mode 100644 index c894266a2..000000000 --- a/src/main/java/appeng/core/PluginLoader.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.core; - -import java.lang.annotation.ElementType; -import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableMap; - -import org.objectweb.asm.Type; - -import net.minecraftforge.fml.ModList; -import net.minecraftforge.forgespi.language.ModFileScanData; - -import appeng.api.AEInjectable; -import appeng.api.AEPlugin; - -/** - * Loads AE plugins on startup and provides them with access to various - * components of the AE API. - */ -class PluginLoader { - - public void loadPlugins(Collection injectables) { - Map, Object> injectableMap = mapInjectables(injectables); - findAndInstantiatePlugins(injectableMap); - } - - private static void findAndInstantiatePlugins(Map, Object> injectableMap) { - Type aType = Type.getType(AEPlugin.class); - Set allAnnotated = ModList.get().getAllScanData().stream() - .map(ModFileScanData::getAnnotations).flatMap(Collection::stream) - .filter(a -> a.getAnnotationType().equals(aType)).filter(a -> a.getTargetType() == ElementType.TYPE) - .collect(Collectors.toSet()); - - for (ModFileScanData.AnnotationData candidate : allAnnotated) { - - String cName = candidate.getMemberName(); - Class aClass; - try { - aClass = Class.forName(cName); - } catch (ClassNotFoundException e) { - AELog.error(e, "Couldn't find annotated AE plugin class " + cName); - throw new RuntimeException("Couldn't find annotated AE plugin class " + cName, e); - } - - // Try instantiating the plugin - try { - Object plugin = instantiatePlugin(aClass, injectableMap); - AELog.info("Loaded AE2 Plugin {}", plugin.getClass()); - } catch (Exception e) { - AELog.error(e, "Unable to instantiate AE plugin " + cName); - throw new RuntimeException("Unable to instantiate AE plugin " + cName, e); - } - } - } - - private static Object instantiatePlugin(Class aClass, Map, Object> injectableMap) throws Exception { - - Constructor[] constructors = aClass.getDeclaredConstructors(); - - if (constructors.length == 0) { - // This is the default no-arg constructor, although it seems pointless to - // instantiate anything but not take - // any AE dependencies as parameters - return aClass.newInstance(); - } else if (constructors.length != 1) { - throw new IllegalArgumentException("Expected a single constructor, but found: " + constructors.length); - } - - Constructor constructor = constructors[0]; - constructor.setAccessible(true); - - Object[] args = findInjectables(constructor, injectableMap); - - return constructor.newInstance(args); - } - - private static Object[] findInjectables(Constructor constructor, Map, Object> injectableMap) { - - Class[] types = constructor.getParameterTypes(); - Object[] args = new Object[types.length]; - - for (int i = 0; i < types.length; i++) { - args[i] = injectableMap.get(types[i]); - if (args[i] == null) { - throw new IllegalArgumentException("Constructor has parameter of type " + types[i] - + " which is not an injectable type." + " Please see the documentation for @AEPlugin."); - } - } - - return args; - } - - private static Map, Object> mapInjectables(Collection injectables) { - ImmutableMap.Builder, Object> builder = ImmutableMap.builder(); - - for (Object injectable : injectables) { - // Get all super-interfaces that were annotated with @AEInjectable - Set> injectableIfs = getInjectableInterfaces(injectable.getClass()); - for (Class injectableIf : injectableIfs) { - builder.put(injectableIf, injectable); - } - } - - return builder.build(); - } - - private static Set> getInjectableInterfaces(Class aClass) { - Set> hierarchy = new HashSet<>(); - getFullHierarchy(aClass, hierarchy); - - return hierarchy.stream().filter(c -> c.getAnnotation(AEInjectable.class) != null).collect(Collectors.toSet()); - } - - // Recursively gather all superclasses and superinterfaces of the given class - // and put them into the given collection - private static void getFullHierarchy(Class aClass, Set> classes) { - classes.add(aClass); - for (Class anIf : aClass.getInterfaces()) { - getFullHierarchy(anIf, classes); - } - if (aClass.getSuperclass() != null) { - getFullHierarchy(aClass.getSuperclass(), classes); - } - } -}