/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.wrapper.mc.forge.v17.asm.lib; import nova.core.component.Component; import nova.core.component.ComponentMap; import nova.core.component.ComponentProvider; import nova.core.component.Passthrough; import nova.core.network.NetworkTarget.Side; import nova.core.util.ClassLoaderUtil; import nova.internal.core.Game; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * The ComponentInjector is capable of creating dynamic classes that implement a * specified super class and implement the interfaces specified by * {@link Component} and {@link Passthrough}. * @param <T> * @author Vic Nightfall */ public class ComponentInjector<T> implements Opcodes { private Class<T> baseClazz; /** * Cache, contains already created class objects for later use */ private Map<List<Class<? extends Component>>, Class<? extends T>> cache = new HashMap<>(); public ComponentInjector(Class<T> baseClazz) { this.baseClazz = baseClazz; } @SuppressWarnings("rawtypes") public synchronized T inject(ComponentProvider<? extends ComponentMap> provider, Class<?>[] typeArgs, Object[] args) { try { List<Component> components = provider.components().stream() .filter(component -> component.getClass().getAnnotationsByType(Passthrough.class).length > 0) .collect(Collectors.toList()); if (components.size() > 0) { List<Class<? extends Component>> componentClazzes = components.stream().map(c -> c.getClass()).collect(Collectors.toList()); Game.logger().info("{} {}", Side.get(), componentClazzes); if (cache.containsKey(componentClazzes)) // Cached class { return inject(cache.get(componentClazzes).getConstructor(typeArgs).newInstance(args), provider); } else { Class<? extends T> clazz = construct(componentClazzes); cache.put(componentClazzes, clazz); return inject(clazz.getConstructor(typeArgs).newInstance(args), provider); } } else { // No components withPriority passthrough interfaces, we can use the // base class. return baseClazz.getConstructor(typeArgs).newInstance(args); } } catch (Exception e) { throw new ClassLoaderUtil.ClassLoaderException("Failed to construct wrapper class for " + baseClazz, e); } } @SuppressWarnings("rawtypes") private T inject(T instance, ComponentProvider provider) throws ReflectiveOperationException { Field f = instance.getClass().getDeclaredField("$$_provider"); f.setAccessible(true); f.set(instance, provider); f.setAccessible(false); return instance; } private Class<? extends T> construct(List<Class<? extends Component>> components) { // Map components to specified wrapped interfaces Map<Class<?>, Class<? extends Component>> intfComponentMap = new HashMap<>(); for (Class<? extends Component> component : components) { for (Passthrough pt : component.getAnnotationsByType(Passthrough.class)) { Class<?> intf; try { intf = Class.forName(pt.value()); } catch (ClassNotFoundException exec) { throw new ClassLoaderUtil.ClassLoaderException("Invalid passthrough \"%s\" on component %s, the specified interface doesn't exist.", pt.value(), component); } if (!intf.isAssignableFrom(component)) { throw new ClassLoaderUtil.ClassLoaderException("Invalid passthrough \"%s\" on component %s, the specified interface isn't implemented.", pt.value(), component); } if (intfComponentMap.containsKey(intf)) { throw new ClassLoaderUtil.ClassLoaderException("Duplicate Passthrough interface found: %s (%s, %s)", pt.value(), component, intfComponentMap.get(intf)); } intfComponentMap.put(intf, component); } } // Create new ClassNode from cached bytes ClassNode clazzNode = new ClassNode(); String name = Type.getInternalName(baseClazz); String classname = name + "_$$_NOVA_" + cache.size(); // Inject block field clazzNode.visit(V1_8, ACC_PUBLIC | ACC_SUPER, classname, null, name, intfComponentMap.keySet().stream().map(Type::getInternalName).toArray(s -> new String[s])); clazzNode.visitField(ACC_PRIVATE | ACC_FINAL, "$$_provider", Type.getDescriptor(ComponentProvider.class), null, null).visitEnd(); // Add constructors for (Constructor<?> constructor : baseClazz.getConstructors()) { int mod = constructor.getModifiers(); String descr = Type.getConstructorDescriptor(constructor); if (Modifier.isFinal(mod) || Modifier.isPrivate(mod)) { continue; } MethodVisitor mv = clazzNode.visitMethod(mod, "<init>", descr, null, ASMHelper.getExceptionTypes(constructor)); // Call super constructor mv.visitCode(); // load this mv.visitVarInsn(ALOAD, 0); Class<?>[] parameters = constructor.getParameterTypes(); for (int i = 0; i < constructor.getParameterCount(); i++) { // variables mv.visitVarInsn(Type.getType(parameters[i]).getOpcode(ILOAD), i + 1); } mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(baseClazz), "<init>", descr, false); // return mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } // Add methods for (Class<?> intf : intfComponentMap.keySet()) { // Create class constant Type clazzConst = Type.getType(intf.getClass()); for (Method m : intf.getMethods()) { boolean isVoid = m.getReturnType() == null; String descr = Type.getMethodDescriptor(m); MethodVisitor mv = clazzNode.visitMethod(ACC_PUBLIC, m.getName(), descr, null, ASMHelper.getExceptionTypes(m)); mv.visitCode(); // load block instance mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, classname, "$$_provider", Type.getDescriptor(ComponentProvider.class)); mv.visitLdcInsn(clazzConst); // load component instance mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(ComponentProvider.class), "get", Type.getMethodDescriptor(Type.getType(Component.class), Type.getType(Class.class)), false); // add parameters Class<?>[] parameters = m.getParameterTypes(); for (int i = 0; i < m.getParameterCount(); i++) { mv.visitVarInsn(Type.getType(parameters[i]).getOpcode(ILOAD), i + 1); } // invoke mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(intf), m.getName(), descr, true); mv.visitInsn(isVoid ? RETURN : Type.getType(m.getReturnType()).getOpcode(IRETURN)); mv.visitMaxs(0, 0); mv.visitEnd(); } } clazzNode.visitEnd(); return ASMHelper.defineClass(clazzNode, ClassWriter.COMPUTE_MAXS, baseClazz.getProtectionDomain()); } }