diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/CloudSimpleHandler.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/CloudSimpleHandler.java index 31c3d90ac..b8a6f6b63 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/cloud/CloudSimpleHandler.java +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/CloudSimpleHandler.java @@ -13,11 +13,17 @@ import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.bergerkiller.bukkit.common.cloud.audiences.PaperAdventureAdapter; import com.bergerkiller.bukkit.common.cloud.captions.BKCommonLibCaptionRegistry; import com.bergerkiller.bukkit.common.cloud.parsers.SoundEffectParser; import com.bergerkiller.bukkit.common.internal.CommonBootstrap; import com.bergerkiller.bukkit.common.resources.ResourceKey; import com.bergerkiller.bukkit.common.resources.SoundEffect; +import com.bergerkiller.mountiplex.reflection.declarations.Template; +import net.kyori.adventure.chat.ChatType; +import net.kyori.adventure.chat.SignedMessage; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; @@ -224,6 +230,34 @@ public class CloudSimpleHandler { // Used by the help system this.bukkitAudiences = BukkitAudiences.create(plugin); + + /* + // Testing! + PaperAdventureAdapter adapter = Template.Class.create(PaperAdventureAdapter.class, Common.TEMPLATE_RESOLVER); + adapter.forceInitialization(); + + { + Object o = adapter.adaptKey(Key.key("test", "world")); + System.out.println("RESULT: " + o); + Key key = adapter.reverseKey(o); + System.out.println("RESULT REV: " + key); + } + + { + Object o = adapter.adaptChatType(ChatType.SAY_COMMAND); + System.out.println("RESULT: " + o); + ChatType type = adapter.reverseChatType(o); + System.out.println("RESULT REV: " + type); + } + + { + Object o = adapter.adaptSignedMessage(SignedMessage.system("cool", Component.text("test"))); + System.out.println("RESULT: " + o); + SignedMessage type = adapter.reverseSignedMessage(o); + System.out.println("RESULT REV: " + type + " " + type.message() + " " + type.unsignedContent()); + } + + */ } private void handleException(CommandSender sender, Throwable exception) throws Throwable { diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceAdapterImpl.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceAdapterImpl.java new file mode 100644 index 000000000..8c455b4d4 --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceAdapterImpl.java @@ -0,0 +1,202 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +import com.bergerkiller.mountiplex.reflection.ReflectionUtil; +import com.bergerkiller.mountiplex.reflection.util.ExtendedClassWriter; +import com.bergerkiller.mountiplex.reflection.util.FastConstructor; +import com.bergerkiller.mountiplex.reflection.util.UniqueHash; +import com.bergerkiller.mountiplex.reflection.util.asm.MPLType; +import net.kyori.adventure.audience.Audience; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.objectweb.asm.Opcodes.*; + +/** + * Generates an implementation type of Audience that calls all the methods on + * another (adapted) Audience implementation. All parameters for these function + * calls are adapted using {@link PaperAdventureAdapter}. + */ +final class AudienceAdapterImpl implements PaperAdventureAdapter.AudienceAdapter { + private static final UniqueHash generatedClassCtr = new UniqueHash(); + private final PaperAdventureAdapter adapter; + private final FastConstructor ctor; + + public AudienceAdapterImpl(PaperAdventureAdapter adapter) { + this.adapter = adapter; + + final Class adaptedAudienceType = adapter.getAdventureClass( + /* String join to avoid the gradle remapper changing it */ + String.join(".", "net", "kyori", "adventure", "audience", "Audience")); + + ExtendedClassWriter cw = ExtendedClassWriter.builder(Audience.class) + .setFlags(ClassWriter.COMPUTE_MAXS) + .setAccess(ACC_FINAL) + .setExactName(AudienceAdapterImpl.class.getName() + "$AudienceImpl" + generatedClassCtr.nextHex()) + .build(); + + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + // Add field storing the PaperAdventureAdapter + { + fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, "adapter", + MPLType.getDescriptor(PaperAdventureAdapter.class), null, null); + fieldVisitor.visitEnd(); + } + + // Add field storing the adapted original Audience + { + fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, "aud", + MPLType.getDescriptor(adaptedAudienceType), null, null); + fieldVisitor.visitEnd(); + } + + // Add constructor assigning both fields + { + String ctorDesc = "(" + MPLType.getDescriptor(PaperAdventureAdapter.class) + MPLType.getDescriptor(adaptedAudienceType) + ")V"; + + methodVisitor = cw.visitMethod(ACC_PUBLIC, "", ctorDesc, null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 1); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), "adapter", MPLType.getDescriptor(PaperAdventureAdapter.class)); + + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 2); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), "aud", MPLType.getDescriptor(adaptedAudienceType)); + + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + + // Implement all methods that feature parameter types that we can adapt + // If we cannot, leave the default implementation unless it is abstract, then throw a NotSupported exception + List methodsToImplement = ReflectionUtil.getAllMethods(Audience.class) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .collect(Collectors.toList()); + + for (Method method : methodsToImplement) { + // Go by all method parameters and the return type and verify that we know how to adapt it + boolean canAdapt = true; + List adaptedParamTypes = new ArrayList<>(); + PaperAdventureAdapter.TypeAdapter adaptedReturnType = null; + for (Class type : method.getParameterTypes()) { + PaperAdventureAdapter.TypeAdapter paramTypeAdapter = adapter.getTypeAdapter(type); + if (paramTypeAdapter == null) { + canAdapt = false; + break; + } else { + adaptedParamTypes.add(paramTypeAdapter); + } + } + if (canAdapt) { + adaptedReturnType = adapter.getTypeAdapter(method.getReturnType()); + if (adaptedReturnType == null) { + canAdapt = false; + } + } + + // Find the exact same method name on the other audience implementation, with the same (adapted) args + Method adaptedMethod = null; + if (canAdapt) { + try { + Class[] params = adaptedParamTypes.stream() + .map(PaperAdventureAdapter.TypeAdapter::getAdaptedType) + .toArray(Class[]::new); + adaptedMethod = adaptedAudienceType.getMethod(method.getName(), params); + if (!adaptedMethod.getReturnType().equals(adaptedReturnType.getAdaptedType())) { + canAdapt = false; + } + } catch (NoSuchMethodException e) { + canAdapt = false; + } + } + + // Fail if we can't implement it + if (!canAdapt) { + System.out.println("Cannot implement: " + method); + if (Modifier.isAbstract(method.getModifiers())) { + cw.visitMethodUnsupported(method, "Not implemented by this Paper Audience adapter"); + } + continue; + } + + System.out.println("Implementing: " + method); + + // Implement the method + // Start by reading the adapter and aud field + methodVisitor = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, MPLType.getName(method), MPLType.getMethodDescriptor(method), null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, cw.getInternalName(), "adapter", MPLType.getDescriptor(PaperAdventureAdapter.class)); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, cw.getInternalName(), "aud", MPLType.getDescriptor(adaptedAudienceType)); + + // Next, go by all parameters and process them with the parameter type mapping we got + int register = 1; + for (PaperAdventureAdapter.TypeAdapter parameterType : adaptedParamTypes) { + register = parameterType.applyParameter(cw, methodVisitor, register); + } + + // Call the same method on the adapted audience + methodVisitor.visitMethodInsn(INVOKEINTERFACE, MPLType.getInternalName(adaptedAudienceType), + method.getName(), MPLType.getMethodDescriptor(adaptedMethod), true); + + // Adapt the return type as well (could emit no instructions if void) + { + adaptedReturnType.applyReturn(cw, methodVisitor); + } + + // Return the value and done + methodVisitor.visitInsn(MPLType.getOpcode(method.getReturnType(), IRETURN)); + methodVisitor.visitMaxs(6, 5); + methodVisitor.visitEnd(); + }; + + // public MyReturnType method(long l, MyParamType type, int k) + + /* + methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "method", "(JLcom/bergerkiller/mountiplex/ASMPlaygroundTest$MyParamType;I)Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$MyReturnType;", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, "com/bergerkiller/mountiplex/ASMPlaygroundTest$AdaptedAudience", "adapter", "Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$Adapter;"); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, "com/bergerkiller/mountiplex/ASMPlaygroundTest$AdaptedAudience", "aud", "Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$OtherAudience;"); + + methodVisitor.visitVarInsn(LLOAD, 1); + + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, "com/bergerkiller/mountiplex/ASMPlaygroundTest$AdaptedAudience", "adapter", "Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$Adapter;"); + methodVisitor.visitVarInsn(ALOAD, 3); + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/bergerkiller/mountiplex/ASMPlaygroundTest$Adapter", "adaptParamType", "(Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$MyParamType;)Ljava/lang/Object;", false); + methodVisitor.visitTypeInsn(CHECKCAST, "com/bergerkiller/mountiplex/ASMPlaygroundTest$OtherMyParamType"); + + methodVisitor.visitVarInsn(ILOAD, 4); + + methodVisitor.visitMethodInsn(INVOKEINTERFACE, "com/bergerkiller/mountiplex/ASMPlaygroundTest$OtherAudience", "method", "(JLcom/bergerkiller/mountiplex/ASMPlaygroundTest$OtherMyParamType;I)Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$OtherMyReturnType;", true); + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/bergerkiller/mountiplex/ASMPlaygroundTest$Adapter", "reverseReturnType", "(Ljava/lang/Object;)Lcom/bergerkiller/mountiplex/ASMPlaygroundTest$MyReturnType;", false); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(6, 5); + methodVisitor.visitEnd(); + */ + + // Generate and done + this.ctor = new FastConstructor<>(cw.generateConstructor(PaperAdventureAdapter.class, adaptedAudienceType)); + } + + @Override + public Audience reverseAudience(Object audience) { + return this.ctor.newInstance(this.adapter, audience); + } +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceProviderAdapterImpl.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceProviderAdapterImpl.java new file mode 100644 index 000000000..9a960650b --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceProviderAdapterImpl.java @@ -0,0 +1,186 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +import com.bergerkiller.mountiplex.reflection.ReflectionUtil; +import com.bergerkiller.mountiplex.reflection.util.ExtendedClassWriter; +import com.bergerkiller.mountiplex.reflection.util.FastConstructor; +import com.bergerkiller.mountiplex.reflection.util.UniqueHash; +import com.bergerkiller.mountiplex.reflection.util.asm.MPLType; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.AudienceProvider; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.IRETURN; +import static org.objectweb.asm.Opcodes.PUTFIELD; +import static org.objectweb.asm.Opcodes.RETURN; + +/** + * Generates an implementation type of AudienceProvider that calls all the methods on + * another (adapted) AudienceProvider implementation. All parameters for these function + * calls are adapted using {@link PaperAdventureAdapter}. + */ +final class AudienceProviderAdapterImpl implements PaperAdventureAdapter.AudienceProviderAdapter { + private static final UniqueHash generatedClassCtr = new UniqueHash(); + private final PaperAdventureAdapter adapter; + private final FastConstructor ctor; + + public AudienceProviderAdapterImpl(PaperAdventureAdapter adapter) { + this.adapter = adapter; + + final Class adaptedAudienceProviderType = adapter.getAdventureClass( + /* String join to avoid the gradle remapper changing it */ + String.join(".", "net", "kyori", "adventure", "platform", "AudienceProvider")); + + ExtendedClassWriter cw = ExtendedClassWriter.builder(AudienceProvider.class) + .setFlags(ClassWriter.COMPUTE_MAXS) + .setAccess(ACC_FINAL) + .setExactName(AudienceProviderAdapterImpl.class.getName() + "$AudienceProviderImpl" + generatedClassCtr.nextHex()) + .build(); + + + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + // Add field storing the PaperAdventureAdapter + { + fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, "adapter", + MPLType.getDescriptor(PaperAdventureAdapter.class), null, null); + fieldVisitor.visitEnd(); + } + + // Add field storing the adapted original Audience + { + fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, "aud", + MPLType.getDescriptor(adaptedAudienceProviderType), null, null); + fieldVisitor.visitEnd(); + } + + // Add constructor assigning both fields + { + String ctorDesc = "(" + MPLType.getDescriptor(PaperAdventureAdapter.class) + MPLType.getDescriptor(adaptedAudienceProviderType) + ")V"; + + methodVisitor = cw.visitMethod(ACC_PUBLIC, "", ctorDesc, null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 1); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), "adapter", MPLType.getDescriptor(PaperAdventureAdapter.class)); + + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 2); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), "aud", MPLType.getDescriptor(adaptedAudienceProviderType)); + + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + + // Implement all methods that feature parameter types that we can adapt + // If we cannot, leave the default implementation unless it is abstract, then throw a NotSupported exception + List methodsToImplement = ReflectionUtil.getAllMethods(Audience.class) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .collect(Collectors.toList()); + + for (Method method : methodsToImplement) { + // Go by all method parameters and the return type and verify that we know how to adapt it + boolean canAdapt = true; + List adaptedParamTypes = new ArrayList<>(); + PaperAdventureAdapter.TypeAdapter adaptedReturnType = null; + for (Class type : method.getParameterTypes()) { + PaperAdventureAdapter.TypeAdapter paramTypeAdapter = adapter.getTypeAdapter(type); + if (paramTypeAdapter == null) { + canAdapt = false; + break; + } else { + adaptedParamTypes.add(paramTypeAdapter); + } + } + if (canAdapt) { + adaptedReturnType = adapter.getTypeAdapter(method.getReturnType()); + if (adaptedReturnType == null) { + canAdapt = false; + } + } + + // Find the exact same method name on the other audience implementation, with the same (adapted) args + Method adaptedMethod = null; + if (canAdapt) { + try { + Class[] params = adaptedParamTypes.stream() + .map(PaperAdventureAdapter.TypeAdapter::getAdaptedType) + .toArray(Class[]::new); + adaptedMethod = adaptedAudienceProviderType.getMethod(method.getName(), params); + if (!adaptedMethod.getReturnType().equals(adaptedReturnType.getAdaptedType())) { + canAdapt = false; + } + } catch (NoSuchMethodException e) { + canAdapt = false; + } + } + + // Fail if we can't implement it + if (!canAdapt) { + System.out.println("Cannot implement: " + method); + if (Modifier.isAbstract(method.getModifiers())) { + cw.visitMethodUnsupported(method, "Not implemented by this Paper Audience adapter"); + } + continue; + } + + System.out.println("Implementing: " + method); + + // Implement the method + // Start by reading the adapter and aud field + methodVisitor = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, MPLType.getName(method), MPLType.getMethodDescriptor(method), null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, cw.getInternalName(), "adapter", MPLType.getDescriptor(PaperAdventureAdapter.class)); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, cw.getInternalName(), "aud", MPLType.getDescriptor(adaptedAudienceProviderType)); + + // Next, go by all parameters and process them with the parameter type mapping we got + int register = 1; + for (PaperAdventureAdapter.TypeAdapter parameterType : adaptedParamTypes) { + register = parameterType.applyParameter(cw, methodVisitor, register); + } + + // Call the same method on the adapted audience + methodVisitor.visitMethodInsn(INVOKEINTERFACE, MPLType.getInternalName(adaptedAudienceProviderType), + method.getName(), MPLType.getMethodDescriptor(adaptedMethod), true); + + // Adapt the return type as well (could emit no instructions if void) + { + adaptedReturnType.applyReturn(cw, methodVisitor); + } + + // Return the value and done + methodVisitor.visitInsn(MPLType.getOpcode(method.getReturnType(), IRETURN)); + methodVisitor.visitMaxs(6, 5); + methodVisitor.visitEnd(); + }; + + // Generate and done + this.ctor = new FastConstructor<>(cw.generateConstructor(PaperAdventureAdapter.class, adaptedAudienceProviderType)); + } + + @Override + public AudienceProvider reverseAudienceProvider(Object audienceProvider) { + return this.ctor.newInstance(this.adapter, audienceProvider); + } +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceTypeAdapter.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceTypeAdapter.java new file mode 100644 index 000000000..6fec0726f --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/AudienceTypeAdapter.java @@ -0,0 +1,51 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +/** + * Type Adapter. These implementations exist: + *
    + *
  • net.kyori.adventure.key.Key
  • + *
  • net.kyori.adventure.text.ComponentLike
  • + *
  • net.kyori.adventure.text.Component
  • + *
  • net.kyori.adventure.audience.MessageType
  • + *
  • net.kyori.adventure.identity.Identified
  • + *
  • net.kyori.adventure.identity.Identity
  • + *
  • net.kyori.adventure.chat.ChatType.Bound
  • + *
  • net.kyori.adventure.chat.SignedMessage
  • + *
  • net.kyori.adventure.chat.SignedMessage.Signature
  • + *
  • net.kyori.adventure.title.Title
  • + *
  • net.kyori.adventure.title.TitlePart
  • + *
+ * + * These are not implemented and these functionalities will not work as a result: + *
    + *
  • net.kyori.adventure.bossbar.BossBar
  • + *
  • net.kyori.adventure.sound.Sound
  • + *
  • net.kyori.adventure.sound.SoundStop
  • + *
  • net.kyori.adventure.sound.Sound.Emitter
  • + *
  • net.kyori.adventure.inventory.Book.Builder
  • + *
  • net.kyori.adventure.inventory.Book
  • + *
  • net.kyori.adventure.resource.ResourcePackInfoLike
  • + *
  • net.kyori.adventure.resource.ResourcePackRequestLike
  • + *
  • net.kyori.adventure.resource.ResourcePackRequest
  • + *
  • net.kyori.adventure.dialog.DialogLike
  • + *
+ * + * @param Type we know + * @param Unknown type + */ +public interface AudienceTypeAdapter { + /** + * Adapts the type we know into the unknown type + * + * @param value Known type + * @return Unknown type + */ + B adapt(A value); + /** + * Restores the unknown type back into a type we know (reverse) + * + * @param value Unknown type + * @return Known type + */ + A restore(B value); +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAdventureAdapter.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAdventureAdapter.java new file mode 100644 index 000000000..783725722 --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAdventureAdapter.java @@ -0,0 +1,691 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +import com.bergerkiller.mountiplex.reflection.declarations.Template; +import com.bergerkiller.mountiplex.reflection.util.BoxedType; +import com.bergerkiller.mountiplex.reflection.util.ExtendedClassWriter; +import com.bergerkiller.mountiplex.reflection.util.asm.MPLType; +import org.objectweb.asm.MethodVisitor; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; + +/** + * Converts types from Adventure between the BKCommonLib shaded types and the + * types declared by Paper in the server.
+ *
+ * These Adventure types can be adapted: + *
    + *
  • net.kyori.adventure.key.audience.Audience
  • + *
  • net.kyori.adventure.key.Key
  • + *
  • net.kyori.adventure.text.Component
  • + *
  • net.kyori.adventure.audience.MessageType
  • + *
  • net.kyori.adventure.identity.Identity
  • + *
  • net.kyori.adventure.chat.ChatType.Bound
  • + *
  • net.kyori.adventure.chat.SignedMessage
  • + *
  • net.kyori.adventure.title.TitlePart
  • + *
+ * + * These are not implemented and these functionalities will not work as a result: + *
    + *
  • net.kyori.adventure.bossbar.BossBar
  • + *
  • net.kyori.adventure.sound.Sound
  • + *
  • net.kyori.adventure.sound.SoundStop
  • + *
  • net.kyori.adventure.sound.Sound.Emitter
  • + *
  • net.kyori.adventure.inventory.Book.Builder
  • + *
  • net.kyori.adventure.inventory.Book
  • + *
  • net.kyori.adventure.resource.ResourcePackInfoLike
  • + *
  • net.kyori.adventure.resource.ResourcePackRequestLike
  • + *
  • net.kyori.adventure.resource.ResourcePackRequest
  • + *
  • net.kyori.adventure.dialog.DialogLike
  • + *
+ */ +@Template.Import("com.bergerkiller.bukkit.common.cloud.audiences.PaperAdventureAdapter") +@Template.InstanceType("net.kyori.adventure.audience.Audience") +public abstract class PaperAdventureAdapter extends Template.Class { + private final Function> adventureClassLoader = getAdventureClassLoader(); + private final Map, TypeAdapter> typeAdapters = new HashMap<>(); + + public final SignedMessageFactory adaptSignedMessageFactory = + new SignedMessageFactoryImpl<>(getAdventureClass( + /* String join to avoid the gradle remapper changing it */ + String.join(".", "net", "kyori", "adventure", "chat", "SignedMessage"))); + public final SignedMessageFactory reverseSignedMessageFactory = + new SignedMessageFactoryImpl<>(net.kyori.adventure.chat.SignedMessage.class); + public final AudienceAdapter adaptAudience = new AudienceAdapterImpl(this); + public final AudienceProviderAdapter adaptAudienceProvider = new AudienceProviderAdapterImpl(this); + + public PaperAdventureAdapter() { + // Register all the pass-through (no adapting) types that can be safely used + // When these types are encountered, it is assumed the adapted method will use the same type + BoxedType.getUnboxedTypes().forEach(this::registerPassThroughAdapter); + BoxedType.getBoxedTypes().forEach(this::registerPassThroughAdapter); + registerPassThroughAdapter(UUID.class); + registerPassThroughAdapter(String.class); + registerPassThroughAdapter(Predicate.class); + registerPassThroughAdapter(Object.class); + + // Register all the type adapters that this class supports + registerStandardAdapter( + net.kyori.adventure.key.Key.class, + String.join(".", "net", "kyori", "adventure", "key", "Key"), + "adaptKey", "reverseKey"); + registerStandardAdapter( + net.kyori.adventure.identity.Identity.class, + String.join(".", "net", "kyori", "adventure", "identity", "Identity"), + "adaptIdentity", "reverseIdentity"); + registerStandardAdapter( + net.kyori.adventure.text.Component.class, + String.join(".", "net", "kyori", "adventure", "text", "Component"), + "adaptComponent", "reverseComponent"); + registerStandardAdapter( + net.kyori.adventure.audience.Audience.class, + String.join(".", "net", "kyori", "adventure", "audience", "Audience"), + "adaptAudience", "reverseAudience"); + registerStandardAdapter( + net.kyori.adventure.platform.AudienceProvider.class, + String.join(".", "net", "kyori", "adventure", "platform", "AudienceProvider"), + "adaptAudienceProvider", "reverseAudienceProvider"); + } + + /** + * Retrieves a (foreign) Adventure library class by name + * + * @param name Name of the class + * @return Class + * @throws UnsupportedOperationException If this type does not exist (somehow?) + */ + public final Class getAdventureClass(String name) { + return adventureClassLoader.apply(name); + } + + /** + * Gets type adapter information for an API Type. + * Returns null if the type is unknown and not supported for adapting. + * + * @param type API Type + * @return TypeAdapter + */ + public final TypeAdapter getTypeAdapter(Class type) { + return typeAdapters.get(type); + } + + /* + * + * public static net.kyori.adventure.key.Key adaptKey(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key key) { + * return net.kyori.adventure.key.Key.key(key.namespace(), key.value()); + * } + */ + @Template.Generated("%ADAPT_KEY%") + public abstract Object adaptKey(net.kyori.adventure.key.Key key); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key reverseKey(net.kyori.adventure.key.Key key) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key.key(key.namespace(), key.value()); + * } + */ + @Template.Generated("%REVERSE_KEY%") + public abstract net.kyori.adventure.key.Key reverseKey(Object key); + + /* + * + * public static net.kyori.adventure.audience.MessageType adaptMessageType(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.audience.MessageType messageType) { + * if (messageType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.audience.MessageType.SYSTEM) { + * return net.kyori.adventure.audience.MessageType.SYSTEM; + * } else { + * return net.kyori.adventure.audience.MessageType.CHAT; + * } + * } + */ + @Template.Generated("%ADAPT_MESSAGE_TYPE%") + public abstract Object adaptMessageType(net.kyori.adventure.key.Key messageType); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.audience.MessageType reverseMessageType(net.kyori.adventure.audience.MessageType messageType) { + * if (messageType == net.kyori.adventure.audience.MessageType.SYSTEM) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.audience.MessageType.SYSTEM; + * } else { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.audience.MessageType.CHAT; + * } + * } + */ + @Template.Generated("%REVERSE_MESSAGE_TYPE%") + public abstract net.kyori.adventure.key.Key reverseMessageType(Object messageType); + + /* + * + * public static net.kyori.adventure.identity.Identity adaptIdentity(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity identity) { + * if (identity == null) { + * return null; + * } else if (identity == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity.nil()) { + * return net.kyori.adventure.identity.Identity.nil(); + * } else { + * return net.kyori.adventure.identity.Identity.identity(identity.uuid()); + * } + * } + */ + @Template.Generated("%ADAPT_IDENTITY%") + public abstract Object adaptIdentity(net.kyori.adventure.identity.Identity identity); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity reverseIdentity(net.kyori.adventure.identity.Identity identity) { + * if (identity == null) { + * return null; + * } else if (identity == net.kyori.adventure.identity.Identity.nil()) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity.nil(); + * } else { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity.identity(identity.uuid()); + * } + * } + */ + @Template.Generated("%REVERSE_IDENTITY%") + public abstract net.kyori.adventure.identity.Identity reverseIdentity(Object identity); + + /* + * + * public static net.kyori.adventure.text.Component adaptComponent(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component component) { + * if (component == null) + * return null; + * if (component == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component.empty()) + * return net.kyori.adventure.text.Component.empty(); + * + * String json = (String) com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(component); + * + * return (net.kyori.adventure.text.Component) + * net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(json); + * } + */ + @Template.Generated("%ADAPT_COMPONENT%") + public abstract Object adaptComponent(net.kyori.adventure.text.Component component); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component reverseComponent(net.kyori.adventure.text.Component component) { + * if (component == null) + * return null; + * if (component == net.kyori.adventure.text.Component.empty()) + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component.empty(); + * + * String json = (String) net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(component); + * + * return (com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component) + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(json); + * } + */ + @Template.Generated("%REVERSE_COMPONENT%") + public abstract net.kyori.adventure.text.Component reverseComponent(Object component); + + /* + * + * public static net.kyori.adventure.chat.ChatType adaptChatType(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType chatType) { + * #if version >= 1.19 + * if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.CHAT) { + * return net.kyori.adventure.chat.ChatType.CHAT; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.SAY_COMMAND) { + * return net.kyori.adventure.chat.ChatType.SAY_COMMAND; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.MSG_COMMAND_INCOMING) { + * return net.kyori.adventure.chat.ChatType.MSG_COMMAND_INCOMING; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.MSG_COMMAND_OUTGOING) { + * return net.kyori.adventure.chat.ChatType.MSG_COMMAND_OUTGOING; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_INCOMING) { + * return net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_INCOMING; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_OUTGOING) { + * return net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_OUTGOING; + * } else if (chatType == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.EMOTE_COMMAND) { + * return net.kyori.adventure.chat.ChatType.EMOTE_COMMAND; + * } else { + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key key = chatType.key(); + * net.kyori.adventure.key.Key adaptedKey = + * net.kyori.adventure.key.Key.key(key.namespace(), key.value()); + * return net.kyori.adventure.chat.ChatType.chatType(adaptedKey); + * } + * #else + * return null; + * #endif + * } + */ + @Template.Generated("%ADAPT_CHAT_TYPE%") + public abstract Object adaptChatType(net.kyori.adventure.chat.ChatType chatType); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType reverseChatType(net.kyori.adventure.chat.ChatType chatType) { + * #if version >= 1.19 + * if (chatType == net.kyori.adventure.chat.ChatType.CHAT) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.CHAT; + * } else if (chatType == net.kyori.adventure.chat.ChatType.SAY_COMMAND) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.SAY_COMMAND; + * } else if (chatType == net.kyori.adventure.chat.ChatType.MSG_COMMAND_INCOMING) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.MSG_COMMAND_INCOMING; + * } else if (chatType == net.kyori.adventure.chat.ChatType.MSG_COMMAND_OUTGOING) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.MSG_COMMAND_OUTGOING; + * } else if (chatType == net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_INCOMING) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_INCOMING; + * } else if (chatType == net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_OUTGOING) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.TEAM_MSG_COMMAND_OUTGOING; + * } else if (chatType == net.kyori.adventure.chat.ChatType.EMOTE_COMMAND) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.EMOTE_COMMAND; + * } else { + * net.kyori.adventure.key.Key key = chatType.key(); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key reversedKey = + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.key.Key.key(key.namespace(), key.value()); + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.chatType(reversedKey); + * } + * #else + * return null; + * #endif + * } + */ + @Template.Generated("%REVERSE_CHAT_TYPE%") + public abstract net.kyori.adventure.chat.ChatType reverseChatType(Object chatType); + + /* + * + * public static net.kyori.adventure.chat.ChatType.Bound reverseChatType(PaperAdventureAdapter adapter, com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.Bound boundChatType) { + * #if version >= 1.19 + * net.kyori.adventure.chat.ChatType type = (net.kyori.adventure.chat.ChatType) adapter.adaptChatType(boundChatType.type()); + * net.kyori.adventure.text.Component name = (net.kyori.adventure.text.Component) adapter.adaptComponent(boundChatType.name()); + * net.kyori.adventure.text.Component target = (net.kyori.adventure.text.Component) adapter.adaptComponent(boundChatType.target()); + * return type.bind(name, target); + * #else + * return null; + * #endif + * } + */ + @Template.Generated("%ADAPT_BOUND_CHAT_TYPE%") + public abstract Object adaptBoundChatType(PaperAdventureAdapter adapter, net.kyori.adventure.chat.ChatType.Bound boundChatType); + + public final Object adaptBoundChatType(net.kyori.adventure.chat.ChatType.Bound boundChatType) { + return adaptBoundChatType(this, boundChatType); + } + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType.Bound reverseChatType(PaperAdventureAdapter adapter, net.kyori.adventure.chat.ChatType.Bound boundChatType) { + * #if version >= 1.19 + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.ChatType type = adapter.reverseChatType(boundChatType.type()); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component name = adapter.reverseComponent(boundChatType.name()); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component target = adapter.reverseComponent(boundChatType.target()); + * return type.bind(name, target); + * #else + * return null; + * #endif + * } + */ + @Template.Generated("%REVERSE_BOUND_CHAT_TYPE%") + public abstract net.kyori.adventure.chat.ChatType.Bound reverseBoundChatType(PaperAdventureAdapter adapter, Object boundChatType); + + public final net.kyori.adventure.chat.ChatType.Bound reverseBoundChatType(Object boundChatType) { + return reverseBoundChatType(this, boundChatType); + } + + /* + * + * public static net.kyori.adventure.chat.SignedMessage.Signature adaptSignedMessageSignature(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage.Signature signature) { + * if (signature == null) + * return null; + * return net.kyori.adventure.chat.SignedMessage.signature(signature.bytes()); + * } + */ + @Template.Generated("%ADAPT_SIGNED_MESSAGE_SIGNATURE%") + public abstract Object adaptSignedMessageSignature(net.kyori.adventure.chat.SignedMessage.Signature signature); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage.Signature reverseSignedMessageSignature(net.kyori.adventure.chat.SignedMessage.Signature signature) { + * if (signature == null) + * return null; + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage.signature(signature.bytes()); + * } + */ + @Template.Generated("%REVERSE_SIGNED_MESSAGE_SIGNATURE%") + public abstract net.kyori.adventure.chat.SignedMessage.Signature reverseSignedMessageSignature(Object signature); + + /* + * + * public static net.kyori.adventure.chat.SignedMessage adaptSignedMessage(PaperAdventureAdapter adapter, com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage signedMessage) { + * if (signedMessage == null) + * return null; + * + * java.time.Instant timestamp = signedMessage.timestamp(); + * long salt = signedMessage.salt(); + * String message = signedMessage.message(); + * + * net.kyori.adventure.chat.SignedMessage$Signature signature = + * (net.kyori.adventure.chat.SignedMessage$Signature) adapter.adaptSignedMessageSignature(signedMessage.signature()); + * net.kyori.adventure.text.Component unsignedContent = + * (net.kyori.adventure.text.Component) adapter.adaptComponent(signedMessage.unsignedContent()); + * net.kyori.adventure.identity.Identity identity = + * (net.kyori.adventure.identity.Identity) adapter.adaptIdentity(signedMessage.identity()); + * + * return (net.kyori.adventure.chat.SignedMessage) adapter.adaptSignedMessageFactory.create( + * timestamp, salt, message, signature, unsignedContent, identity ); + * } + */ + @Template.Generated("%ADAPT_SIGNED_MESSAGE%") + public abstract Object adaptSignedMessage(PaperAdventureAdapter adapter, net.kyori.adventure.chat.SignedMessage signedMessage); + + public final Object adaptSignedMessage(net.kyori.adventure.chat.SignedMessage signedMessage) { + return adaptSignedMessage(this, signedMessage); + } + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage reverseSignedMessage(PaperAdventureAdapter adapter, net.kyori.adventure.chat.SignedMessage signedMessage) { + * if (signedMessage == null) + * return null; + * + * java.time.Instant timestamp = signedMessage.timestamp(); + * long salt = signedMessage.salt(); + * String message = signedMessage.message(); + * + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage$Signature signature = + * adapter.reverseSignedMessageSignature(signedMessage.signature()); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component unsignedContent = + * adapter.reverseComponent(signedMessage.unsignedContent()); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.identity.Identity identity = + * adapter.reverseIdentity(signedMessage.identity()); + * + * return (com.bergerkiller.bukkit.common.dep.net.kyori.adventure.chat.SignedMessage) adapter.reverseSignedMessageFactory.create( + * timestamp, salt, message, signature, unsignedContent, identity ); + * } + */ + @Template.Generated("%REVERSE_SIGNED_MESSAGE%") + public abstract net.kyori.adventure.chat.SignedMessage reverseSignedMessage(PaperAdventureAdapter adapter, Object signedMessage); + + public final net.kyori.adventure.chat.SignedMessage reverseSignedMessage(Object signedMessage) { + return reverseSignedMessage(this, signedMessage); + } + + /* + * + * public static net.kyori.adventure.title.Title adaptTitle(PaperAdventureAdapter adapter, com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title title) { + * if (title == null) + * return null; + * + * // Adapt times + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title$Times origTimes = title.times(); + * net.kyori.adventure.title.Title$Times times; + * if (origTimes == null) { + * times = null; + * } else if (origTimes == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title.DEFAULT_TIMES) { + * times = net.kyori.adventure.title.Title.DEFAULT_TIMES; + * } else { + * times = net.kyori.adventure.title.Title$Times.times( + * origTimes.fadeIn(), origTimes.stay(), origTimes.fadeOut()); + * } + * + * // Adapt the other elements + * net.kyori.adventure.text.Component titleText = + * (net.kyori.adventure.text.Component) adapter.adaptComponent(title.title()); + * net.kyori.adventure.text.Component subTitleText = + * (net.kyori.adventure.text.Component) adapter.adaptComponent(title.subtitle()); + * + * // Build + * return net.kyori.adventure.title.Title.title(titleText, subTitleText, times); + * } + */ + @Template.Generated("%ADAPT_TITLE%") + public abstract Object adaptTitle(PaperAdventureAdapter adapter, net.kyori.adventure.title.Title title); + + public final Object adaptTitle(net.kyori.adventure.title.Title title) { + return adaptTitle(this, title); + } + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title reverseTitle(PaperAdventureAdapter adapter, net.kyori.adventure.title.Title title) { + * if (title == null) + * return null; + * + * // Reverse times + * net.kyori.adventure.title.Title$Times origTimes = title.times(); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title$Times times; + * if (origTimes == null) { + * times = null; + * } else if (origTimes == net.kyori.adventure.title.Title.DEFAULT_TIMES) { + * times = com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title.DEFAULT_TIMES; + * } else { + * times = com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title$Times.times( + * origTimes.fadeIn(), origTimes.stay(), origTimes.fadeOut()); + * } + * + * // Reverse the other elements + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component titleText = + * adapter.reverseComponent(title.title()); + * com.bergerkiller.bukkit.common.dep.net.kyori.adventure.text.Component subTitleText = + * adapter.reverseComponent(title.subtitle()); + * + * // Build + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.Title.title(titleText, subTitleText, times); + * } + */ + @Template.Generated("%REVERSE_TITLE%") + public abstract net.kyori.adventure.title.Title reverseTitle(PaperAdventureAdapter adapter, Object title); + + public final net.kyori.adventure.title.Title reverseTitle(Object title) { + return reverseTitle(this, title); + } + + /* + * + * public static net.kyori.adventure.title.TitlePart adaptMessageType(com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart titlePart) { + * if (titlePart == null) { + * return null; + * } else if (titlePart == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.TITLE) { + * return net.kyori.adventure.title.TitlePart.TITLE; + * } else if (titlePart == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.SUBTITLE) { + * return net.kyori.adventure.title.TitlePart.SUBTITLE; + * } else if (titlePart == com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.TIMES) { + * return net.kyori.adventure.title.TitlePart.TIMES; + * } else { + * throw new IllegalArgumentException("Unknown title part: " + titlePart); + * } + * } + */ + @Template.Generated("%ADAPT_TITLE_PART%") + public abstract Object adaptTitlePart(net.kyori.adventure.title.TitlePart titlePart); + + /* + * + * public static com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart reverseMessageType(net.kyori.adventure.title.TitlePart titlePart) { + * if (titlePart == null) { + * return null; + * } else if (titlePart == net.kyori.adventure.title.TitlePart.TITLE) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.TITLE; + * } else if (titlePart == net.kyori.adventure.title.TitlePart.SUBTITLE) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.SUBTITLE; + * } else if (titlePart == net.kyori.adventure.title.TitlePart.TIMES) { + * return com.bergerkiller.bukkit.common.dep.net.kyori.adventure.title.TitlePart.TIMES; + * } else { + * throw new IllegalArgumentException("Unknown title part: " + titlePart); + * } + * } + */ + @Template.Generated("%REVERSE_TITLE_PART%") + public abstract net.kyori.adventure.title.TitlePart reverseTitlePart(Object titlePart); + + public final net.kyori.adventure.audience.Audience reverseAudience(Object audience) { + return adaptAudience.reverseAudience(audience); + } + + public final net.kyori.adventure.platform.AudienceProvider reverseAudienceProvider(Object audienceProvider) { + return adaptAudienceProvider.reverseAudienceProvider(audienceProvider); + } + + /** + * Registers a Type Adapter that performs no adapting at all, passing it through. + * + * @param type Type + */ + private void registerPassThroughAdapter( + final Class type + ) { + registerTypeAdapter(new TypeAdapter() { + @Override + public Class getType() { + return type; + } + + @Override + public Class getAdaptedType() { + return type; + } + + @Override + public int applyParameter(ExtendedClassWriter cw, MethodVisitor mv, int register) { + return MPLType.visitVarILoad(mv, register, type); + } + + @Override + public void applyReturn(ExtendedClassWriter cw, MethodVisitor mv) { + } + }); + } + + private void registerStandardAdapter( + final Class type, /* For example: net.kyori.adventure.title.TitlePart.class */ + final String adaptedTypeName, /* For example: "net.kyori.adventure.title.TitlePart" */ + final String adaptMethodName, /* For example: "adaptTitlePart" */ + final String reverseMethodName /* For example: "reverseTitlePart" */ + ) { + final Class adaptedType = getAdventureClass(adaptedTypeName); + registerTypeAdapter(new TypeAdapter() { + @Override + public Class getType() { + return type; + } + + @Override + public Class getAdaptedType() { + return adaptedType; + } + + @Override + public int applyParameter(ExtendedClassWriter cw, MethodVisitor mv, int register) { + // Access 'adapter' field of the adapter class we generate + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, cw.getInternalName(), "adapter", MPLType.getDescriptor(PaperAdventureAdapter.class)); + + // Load the parameter from the stack, and adapt it + int nextRegister = MPLType.visitVarILoad(mv, register, type); + mv.visitMethodInsn(INVOKEVIRTUAL, MPLType.getInternalName(PaperAdventureAdapter.class), + adaptMethodName, "(" + MPLType.getDescriptor(type) + ")Ljava/lang/Object;", false); + mv.visitTypeInsn(CHECKCAST, MPLType.getInternalName(adaptedType)); + return nextRegister; + } + + @Override + public void applyReturn(ExtendedClassWriter cw, MethodVisitor mv) { + mv.visitMethodInsn(INVOKEVIRTUAL, MPLType.getInternalName(PaperAdventureAdapter.class), reverseMethodName, + "(Ljava/lang/Object;)" + MPLType.getDescriptor(type), false); + } + }); + } + + private void registerTypeAdapter(TypeAdapter typeAdapter) { + typeAdapters.put(typeAdapter.getType(), typeAdapter); + } + + /** + * Looks up the (foreign) Adventure library Class and then initializes a loader for other + * types in the Adventure library. + * + * @return Loader + */ + private static Function> getAdventureClassLoader() { + final String prefix; + final ClassLoader classLoader; + try { + String audienceName = String.join(".", "net", "kyori", "adventure", "audience", "Audience"); + Class audienceType = Class.forName(audienceName); + prefix = audienceType.getName().endsWith(audienceName) + ? audienceType.getName().substring(0, audienceType.getName().length() - audienceName.length()) + : ""; + classLoader = audienceType.getClassLoader(); + } catch (ClassNotFoundException ex) { + throw new UnsupportedOperationException("Native Adventure library could not be found on the server"); + } + + return name -> { + try { + return Class.forName(prefix + name, false, classLoader); + } catch (ClassNotFoundException ex) { + throw new UnsupportedOperationException("Adventure library type not found: " + name); + } + }; + } + + public interface SignedMessageFactory { + T create( + final java.time.Instant timestamp, + final long salt, + final String message, + final Object /* net.kyori.adventure.chat.SignedMessage$Signature */ signature, + final Object /* net.kyori.adventure.text.Component */ unsignedContent, + final Object /* net.kyori.adventure.identity.Identity */ identity + ); + } + + public interface AudienceAdapter { + net.kyori.adventure.audience.Audience reverseAudience( + final Object /* net.kyori.adventure.audience.Audience */ audience + ); + } + + public interface AudienceProviderAdapter { + net.kyori.adventure.platform.AudienceProvider reverseAudienceProvider( + final Object /* net.kyori.adventure.platform.AudienceProvider */ audienceProvider + ); + } + + /** + * A single type adapter responsible for converting from a public API Type to an adapted + * (foreign) Adventure type. Handles generating the required ASM for converting + * parameter types before calling the foreign method and converting the method result + * back into API types. + */ + public interface TypeAdapter { + /** + * Gets the API type (known to BKCommonLib at compile time) + * + * @return API Type + */ + Class getType(); + + /** + * Gets the adapted type (not known to BKCommonLib at compile time) + * + * @return Adapted Type + */ + Class getAdaptedType(); + + /** + * Adapts the type from API type to Adapted Type for a method parameter + * + * @param cw ExtendedClassWriter + * @param mv MethodVisitor + * @param register Parameter register position + * @return Parameter register position for the next parameter (register + size) + */ + int applyParameter(ExtendedClassWriter cw, MethodVisitor mv, int register); + + /** + * Adapts the type from Adapted Type to API type for a return value + * + * @param cw ExtendedClassWriter + * @param mv MethodVisitor + */ + void applyReturn(ExtendedClassWriter cw, MethodVisitor mv); + } +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAudienceAdapterFactoryOld.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAudienceAdapterFactoryOld.java new file mode 100644 index 000000000..f9ef5b0f3 --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/PaperAudienceAdapterFactoryOld.java @@ -0,0 +1,135 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +import com.bergerkiller.mountiplex.reflection.util.ExtendedClassWriter; +import com.bergerkiller.mountiplex.reflection.util.FastConstructor; +import com.bergerkiller.mountiplex.reflection.util.asm.MPLType; +import net.kyori.adventure.audience.Audience; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +import static org.objectweb.asm.Opcodes.*; + +/** + * Because we shade in the Adventure library at a path different than Paper, we cannot + * make use of Paper's own Audience implementation. This class generates an adapter + * class that calls into Paper's Audience methods from (remapped) our own.
+ *
+ * To avoid making this a nightmare to maintain, it generates most of the adapter code. + */ +public class PaperAudienceAdapterFactoryOld { + private final FastConstructor audienceAdapterCtor = new FastConstructor<>(); + + public PaperAudienceAdapterFactoryOld() throws Throwable { + + + } + + /* + *
  • net.kyori.adventure.key.Key
  • + *
  • net.kyori.adventure.text.ComponentLike
  • Don't implement, use default implementation + *
  • net.kyori.adventure.text.Component
  • Implement, with GSON String as intermediate + *
  • net.kyori.adventure.audience.MessageType
  • Implement, just CHAT or SYSTEM + *
  • net.kyori.adventure.identity.Identified
  • Don't implement, use default implementation + *
  • net.kyori.adventure.identity.Identity
  • Implement, just has UUID field + *
  • net.kyori.adventure.chat.ChatType.Bound
  • Implement, and ChatType too + *
  • net.kyori.adventure.chat.SignedMessage
  • Implement, has simple types (+Component) + *
  • net.kyori.adventure.chat.SignedMessage.Signature
  • Implement, is just byte[] + *
  • net.kyori.adventure.title.Title
  • Implement, has Times (Duration) and some components + *
  • net.kyori.adventure.title.TitlePart
  • Implements, it's like an enum + */ + + private static Class createAudienceAdapterClass(Class paperAudienceType) { + ExtendedClassWriter cw = ExtendedClassWriter.builder(Audience.class) + .setFlags(ClassWriter.COMPUTE_MAXS) + .setAccess(ACC_FINAL) + .setExactName("com.bergerkiller.bukkit.common.cloud.audiences.PaperAudienceAdapterFactoryOld$AudienceAdapter") + .build(); + + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + // Add field storing the Paper audience implementation type + { + fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, "aud", + MPLType.getDescriptor(paperAudienceType), null, null); + fieldVisitor.visitEnd(); + } + + // Generate a constructor that assigns this Paper audience field + { + methodVisitor = cw.visitMethod(ACC_PUBLIC, "", "(" + MPLType.getDescriptor(paperAudienceType) + ")V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 1); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), "aud", MPLType.getDescriptor(paperAudienceType)); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + + // Go by all methods that exist in the Audience class, and implement them by calling + // the same method on the 'aud' field. If the method is not available, and the method + // is abstract (no default implementation), implements it to throw an UnsupportedOperationException + + + + { + methodVisitor = cw.visitMethod(ACC_PUBLIC, "doThing", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitLdcInsn("Not Supported"); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "", "(Ljava/lang/String;)V", false); + methodVisitor.visitInsn(ATHROW); + methodVisitor.visitMaxs(3, 1); + methodVisitor.visitEnd(); + } + cw.visitEnd(); + + //return cw.toByteArray(); + return null; + } + + /** + * Type Adapter. These implementations exist: + *
      + *
    • net.kyori.adventure.key.Key
    • + *
    • net.kyori.adventure.text.ComponentLike
    • + *
    • net.kyori.adventure.text.Component
    • + *
    • net.kyori.adventure.audience.MessageType
    • + *
    • net.kyori.adventure.identity.Identified
    • + *
    • net.kyori.adventure.identity.Identity
    • + *
    • net.kyori.adventure.chat.ChatType.Bound
    • + *
    • net.kyori.adventure.chat.SignedMessage
    • + *
    • net.kyori.adventure.chat.SignedMessage.Signature
    • + *
    • net.kyori.adventure.title.Title
    • + *
    • net.kyori.adventure.title.TitlePart
    • + *
    + * + * These are not implemented and these functionalities will not work as a result: + *
      + *
    • net.kyori.adventure.bossbar.BossBar
    • + *
    • net.kyori.adventure.sound.Sound
    • + *
    • net.kyori.adventure.sound.SoundStop
    • + *
    • net.kyori.adventure.sound.Sound.Emitter
    • + *
    • net.kyori.adventure.inventory.Book.Builder
    • + *
    • net.kyori.adventure.inventory.Book
    • + *
    • net.kyori.adventure.resource.ResourcePackInfoLike
    • + *
    • net.kyori.adventure.resource.ResourcePackRequestLike
    • + *
    • net.kyori.adventure.resource.ResourcePackRequest
    • + *
    • net.kyori.adventure.dialog.DialogLike
    • + *
    + * + * @param Type we know + * @param Unknown type + */ + public interface TypeAdapter { + /** Adapts the type we know into the unknown type */ + B adapt(A value); + /** Restores the unknown type back into a type we know (reverse) */ + A restore(B value); + } +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/SignedMessageFactoryImpl.java b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/SignedMessageFactoryImpl.java new file mode 100644 index 000000000..ad26cef41 --- /dev/null +++ b/src/main/java/com/bergerkiller/bukkit/common/cloud/audiences/SignedMessageFactoryImpl.java @@ -0,0 +1,131 @@ +package com.bergerkiller.bukkit.common.cloud.audiences; + +import com.bergerkiller.mountiplex.reflection.util.ExtendedClassWriter; +import com.bergerkiller.mountiplex.reflection.util.FastConstructor; +import com.bergerkiller.mountiplex.reflection.util.UniqueHash; +import com.bergerkiller.mountiplex.reflection.util.asm.MPLType; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.objectweb.asm.Opcodes.*; + +/** + * Generates implementation types for net.kyori.adventure.chat.SignedMessage. + */ +final class SignedMessageFactoryImpl implements PaperAdventureAdapter.SignedMessageFactory { + private static final UniqueHash generatedClassCtr = new UniqueHash(); + private final FastConstructor ctor; + + public SignedMessageFactoryImpl(Class signatureClass) { + this.ctor = new FastConstructor<>(generateConstructor(signatureClass)); + } + + @Override + public T create( + final java.time.Instant timestamp, + final long salt, + final String message, + final Object /* net.kyori.adventure.chat.SignedMessage$Signature */ signature, + final Object /* net.kyori.adventure.text.Component */ unsignedContent, + final Object /* net.kyori.adventure.identity.Identity */ identity + ) { + return ctor.newInstanceVA(timestamp, salt, message, signature, unsignedContent, identity); + } + + private static Constructor generateConstructor(Class signatureClass) { + ExtendedClassWriter cw = ExtendedClassWriter.builder(signatureClass) + .setFlags(ClassWriter.COMPUTE_MAXS) + .setAccess(ACC_FINAL) + .setExactName(SignedMessageFactoryImpl.class.getName() + "$SignedMessageImpl" + generatedClassCtr.nextHex()) + .build(); + + MethodVisitor methodVisitor; + + // We need these types + Class signedMessageSignatureType = findAdventureType(signatureClass, "chat.SignedMessage$Signature"); + Class componentType = findAdventureType(signatureClass, "text.Component"); + Class identityType = findAdventureType(signatureClass, "identity.Identity"); + + // Declare all fields in our type that will also be set as constructor parameters + List fields = Arrays.asList( + new FieldInfo("timestamp", "timestamp", java.time.Instant.class), + new FieldInfo("salt", "salt", long.class), + new FieldInfo("message", "message", String.class), + new FieldInfo("signature", "signature", signedMessageSignatureType), + new FieldInfo("unsignedContent", "unsignedContent", componentType), + new FieldInfo("identity", "identity", identityType) + ); + + // Add fields as private final fields + for (FieldInfo field : fields) { + FieldVisitor fieldVisitor = cw.visitField(ACC_PRIVATE | ACC_FINAL, field.name, + MPLType.getDescriptor(field.type), null, null); + fieldVisitor.visitEnd(); + } + + // Generate a constructor that assigns all these fields + { + String ctorDesc = "(" + fields.stream().map(f -> MPLType.getDescriptor(f.type)).collect(Collectors.joining()) + ")V"; + + methodVisitor = cw.visitMethod(ACC_PUBLIC, "", ctorDesc, null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + + int register = 1; + for (FieldInfo field : fields) { + methodVisitor.visitVarInsn(ALOAD, 0); + register = MPLType.visitVarILoad(methodVisitor, register, field.type); + methodVisitor.visitFieldInsn(PUTFIELD, cw.getInternalName(), field.name, MPLType.getDescriptor(field.type)); + } + + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + + // Implement all the getter methods that return our fields + for (FieldInfo field : fields) { + methodVisitor = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, field.getterName, + "()" + MPLType.getDescriptor(field.type), null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn(GETFIELD, cw.getInternalName(), field.name, MPLType.getDescriptor(field.type)); + methodVisitor.visitInsn(MPLType.getOpcode(field.type, IRETURN)); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + } + + // Generate the class and retrieve its constructor + return cw.generateConstructor(fields.stream().map(f -> f.type).toArray(Class[]::new)); + } + + private static Class findAdventureType(Class signedMessageType, String adventureTypeName) { + // Extract package from signedMessageType (net.kyori.adventure.chat.SignedMessage -> net.kyori.adventure) + String prefix = signedMessageType.getName().substring(0, signedMessageType.getName().length() - 18); + String fullTypeName = prefix + adventureTypeName; + try { + return Class.forName(fullTypeName, false, signedMessageType.getClassLoader()); + } catch (ClassNotFoundException ex) { + throw new UnsupportedOperationException("Adventure library type not found: " + fullTypeName); + } + } + + private static class FieldInfo { + public final String name; + public final Class type; + public final String getterName; + + public FieldInfo(String name, String getterName, Class type) { + this.name = name; + this.getterName = getterName; + this.type = type; + } + } +} diff --git a/src/main/java/com/bergerkiller/bukkit/common/internal/CommonPlugin.java b/src/main/java/com/bergerkiller/bukkit/common/internal/CommonPlugin.java index a2cc0306f..5604ef108 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/internal/CommonPlugin.java +++ b/src/main/java/com/bergerkiller/bukkit/common/internal/CommonPlugin.java @@ -58,6 +58,8 @@ import com.bergerkiller.mountiplex.MountiplexUtil; import com.bergerkiller.mountiplex.reflection.SafeField; import com.bergerkiller.mountiplex.reflection.util.asm.ASMUtil; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -74,6 +76,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.generator.BlockPopulator; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.incendo.cloud.minecraft.extras.AudienceProvider; import java.io.File; import java.lang.ref.SoftReference; @@ -676,6 +679,18 @@ public class CommonPlugin extends PluginBase { return; } + new Task(this) { + @Override + public void run() { + try (BukkitAudiences aud = BukkitAudiences.create(this.getPlugin())) { + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage("OLD STYLE HELLO"); + aud.player(player).sendMessage(Component.text("Hello!")); + } + } + } + }.start(20, 20); + // Welcome message final List welcomeMessages = Arrays.asList( "This library is written with stability in mind.",