From f80ce156c308160e5a413f65ecc3bb8779ea7e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=91?= Date: Thu, 19 Sep 2024 22:21:30 +0800 Subject: [PATCH] =?UTF-8?q?[6.2.0][dev]=20=E7=A7=BB=E9=99=A4=20XSeries=20S?= =?UTF-8?q?kull=EF=BC=8C=E5=90=88=E5=B9=B6=20database-player=EF=BC=8C?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/configuration/BukkitYaml.java | 34 +- .../configuration/YamlConstructor.java | 36 + .../configuration/YamlRepresenter.java | 17 + .../configuration/YamlCommentLoader.java | 9 +- .../configuration/ConfigurationSection.kt | 532 ++++++----- .../module/configuration/ConfigFile.kt | 61 ++ .../module/configuration/Converters.kt | 12 + .../module/configuration/YamlParser.kt | 10 +- .../module/configuration/YamlWriter.kt | 2 +- .../taboolib/platform/util/BukkitSkull.kt | 181 ++++ .../bukkit-xseries-item/build.gradle.kts | 32 - .../xseries/profiles/PlayerProfiles.java | 220 ----- .../library/xseries/profiles/PlayerUUIDs.java | 136 --- .../xseries/profiles/ProfileLogger.java | 14 - .../xseries/profiles/ProfilesCore.java | 194 ---- .../profiles/builder/ProfileFallback.java | 36 - .../profiles/builder/ProfileInstruction.java | 234 ----- .../xseries/profiles/builder/XSkull.java | 152 ---- .../InvalidProfileContainerException.java | 14 - .../exceptions/InvalidProfileException.java | 16 - .../exceptions/MojangAPIException.java | 14 - .../exceptions/MojangAPIRetryException.java | 26 - .../exceptions/ProfileChangeException.java | 16 - .../profiles/exceptions/ProfileException.java | 25 - .../exceptions/UnknownPlayerException.java | 12 - .../profiles/mojang/MinecraftClient.java | 271 ------ .../xseries/profiles/mojang/MojangAPI.java | 355 -------- .../profiles/mojang/MojangProfileCache.java | 96 -- .../profiles/mojang/PlayerProfile.java | 26 - .../mojang/PlayerProfileFetcherThread.java | 29 - .../mojang/ProfileRequestConfiguration.java | 5 - .../xseries/profiles/mojang/RateLimiter.java | 115 --- .../profiles/objects/ProfileContainer.java | 150 ---- .../profiles/objects/ProfileInputType.java | 144 --- .../xseries/profiles/objects/Profileable.java | 339 ------- .../objects/cache/CacheableProfileable.java | 70 -- .../cache/TimedCacheableProfileable.java | 52 -- .../transformer/ProfileTransformer.java | 117 --- .../transformer/TransformableProfile.java | 80 -- .../reflection/AggregateReflectiveHandle.java | 94 -- .../xseries/reflection/ReflectiveHandle.java | 91 -- .../xseries/reflection/ReflectiveMapping.java | 15 - .../reflection/ReflectiveNamespace.java | 141 --- .../xseries/reflection/VersionHandle.java | 98 -- .../xseries/reflection/XReflection.java | 654 -------------- .../constraint/ReflectiveConstraint.java | 13 - .../constraint/VisibilityConstraint.java | 19 - .../jvm/ConstructorMemberHandle.java | 74 -- .../reflection/jvm/EnumMemberHandle.java | 112 --- .../reflection/jvm/FieldMemberHandle.java | 209 ----- .../jvm/FlaggedNamedMemberHandle.java | 59 -- .../xseries/reflection/jvm/MemberHandle.java | 66 -- .../reflection/jvm/MethodMemberHandle.java | 126 --- .../reflection/jvm/NamedMemberHandle.java | 44 - .../reflection/jvm/NamedReflectiveHandle.java | 13 - .../reflection/jvm/classes/ClassHandle.java | 111 --- .../jvm/classes/DynamicClassHandle.java | 130 --- .../reflection/jvm/classes/PackageHandle.java | 28 - .../jvm/classes/StaticClassHandle.java | 74 -- .../minecraft/MinecraftClassHandle.java | 52 -- .../minecraft/MinecraftConnection.java | 124 --- .../minecraft/MinecraftMapping.java | 11 - .../minecraft/MinecraftPackage.java | 37 - .../reflection/minecraft/NMSExtras.java | 845 ------------------ .../reflection/parser/ReflectionParser.java | 281 ------ module/bukkit/bukkit-xseries/build.gradle.kts | 6 + .../taboolib/library/xseries/XItemStack.java | 35 +- .../taboolib/library/xseries/XPotion.java | 24 +- .../xseries/{XMaterialUtil.kt => Alias.kt} | 0 .../taboolib/library/xseries/XItemStack.kt | 34 + .../taboolib/platform/util/BookBuilder.kt | 0 .../taboolib/platform/util/BukkitBook.kt | 0 .../taboolib/platform/util/ItemBuilder.kt | 15 +- .../taboolib/platform/util/ItemSkull.kt | 11 +- .../taboolib/expansion/AutoDataContainer.kt | 110 +++ .../taboolib/expansion/DataContainer.kt | 81 +- .../kotlin/taboolib/expansion/Database.kt | 87 +- .../taboolib/expansion/DatabaseHandler.kt | 143 +++ .../expansion/DatabaseHandlerForBukkit.kt | 29 + .../taboolib/expansion/MultipleHandler.kt | 125 +++ .../expansion/MultipleHandlerListener.kt | 25 + .../main/kotlin/taboolib/expansion/Type.kt | 13 + .../main/kotlin/taboolib/expansion/TypeSQL.kt | 21 +- .../kotlin/taboolib/expansion/TypeSQLite.kt | 30 +- settings.gradle.kts | 1 - 85 files changed, 1349 insertions(+), 6846 deletions(-) create mode 100644 module/bukkit/bukkit-util/src/main/kotlin/taboolib/platform/util/BukkitSkull.kt delete mode 100644 module/bukkit/bukkit-xseries-item/build.gradle.kts delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerProfiles.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerUUIDs.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfileLogger.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfilesCore.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileFallback.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileInstruction.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/XSkull.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileContainerException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIRetryException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileChangeException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/UnknownPlayerException.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MinecraftClient.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangAPI.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangProfileCache.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfile.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfileFetcherThread.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/ProfileRequestConfiguration.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/RateLimiter.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileContainer.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileInputType.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/Profileable.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/CacheableProfileable.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/TimedCacheableProfileable.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/ProfileTransformer.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/TransformableProfile.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/AggregateReflectiveHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveMapping.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveNamespace.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/VersionHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/XReflection.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/ReflectiveConstraint.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/VisibilityConstraint.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/ConstructorMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/EnumMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FieldMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FlaggedNamedMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MethodMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedMemberHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedReflectiveHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/ClassHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/DynamicClassHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/PackageHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/StaticClassHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftClassHandle.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftConnection.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftMapping.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftPackage.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/NMSExtras.java delete mode 100644 module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/parser/ReflectionParser.java rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/java/taboolib/library/xseries/XItemStack.java (98%) rename module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/{XMaterialUtil.kt => Alias.kt} (100%) rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/kotlin/taboolib/library/xseries/XItemStack.kt (50%) rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/kotlin/taboolib/platform/util/BookBuilder.kt (100%) rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/kotlin/taboolib/platform/util/BukkitBook.kt (100%) rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt (94%) rename module/bukkit/{bukkit-xseries-item => bukkit-xseries}/src/main/kotlin/taboolib/platform/util/ItemSkull.kt (62%) create mode 100644 module/database/database-player/src/main/kotlin/taboolib/expansion/AutoDataContainer.kt create mode 100644 module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandler.kt create mode 100644 module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandlerListener.kt diff --git a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/BukkitYaml.java b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/BukkitYaml.java index c5f46a1d4..c7b1ffa6f 100644 --- a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/BukkitYaml.java +++ b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/BukkitYaml.java @@ -20,33 +20,61 @@ import java.util.ArrayDeque; import java.util.Queue; +/** + * BukkitYaml 类扩展了 Yaml 类,提供了对 YAML 文件的特定处理功能。 + * 这个类主要用于处理 Bukkit 配置文件中的注释和序列化。 + */ public class BukkitYaml extends Yaml { + /** 用于存储 Emitter 类中的 events 字段 */ private static final Field events; + /** 用于存储 Emitter 类中的 blockCommentsCollector 字段 */ private static final Field blockCommentsCollector; + /** 用于存储 Emitter 类中的 inlineCommentsCollector 字段 */ private static final Field inlineCommentsCollector; + /** + * 获取 Emitter 类中指定名称的字段。 + * + * @param name 字段名称 + * @return 返回对应的 Field 对象,如果获取失败则返回 null + */ private static Field getEmitterField(String name) { Field field = null; try { field = Emitter.class.getDeclaredField(name); field.setAccessible(true); } catch (ReflectiveOperationException ex) { - // Ignore as a fail-safe fallback + // 忽略异常,作为一个安全的回退机制 } return field; } + // 静态初始化块,用于初始化静态字段 static { events = getEmitterField("events"); blockCommentsCollector = getEmitterField("blockCommentsCollector"); inlineCommentsCollector = getEmitterField("inlineCommentsCollector"); } + /** + * 构造函数,初始化 BukkitYaml 实例。 + * + * @param constructor YAML 构造器 + * @param representer YAML 表示器 + * @param dumperOptions 转储选项 + * @param loadingConfig 加载配置 + */ public BukkitYaml(@NotNull BaseConstructor constructor, @NotNull Representer representer, @NotNull DumperOptions dumperOptions, @NotNull LoaderOptions loadingConfig) { super(constructor, representer, dumperOptions, loadingConfig); } + /** + * 重写序列化方法,用于处理 YAML 节点的序列化。 + * + * @param node 要序列化的 YAML 节点 + * @param output 输出写入器 + */ @Override public void serialize(@NotNull Node node, @NotNull Writer output) { Emitter emitter = new Emitter(output, dumperOptions); @@ -57,8 +85,8 @@ public void serialize(@NotNull Node node, @NotNull Writer output) { blockCommentsCollector.set(emitter, new CommentEventsCollector(newEvents, CommentType.BLANK_LINE, CommentType.BLOCK)); inlineCommentsCollector.set(emitter, new CommentEventsCollector(newEvents, CommentType.IN_LINE)); } catch (ReflectiveOperationException ex) { - // Don't ignore this as we could be in an inconsistent state - throw new RuntimeException("Could not update Yaml event queue", ex); + // 不要忽略这个异常,因为我们可能处于不一致的状态 + throw new RuntimeException("无法更新 Yaml 事件队列", ex); } } Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null); diff --git a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlConstructor.java b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlConstructor.java index 5bad50c88..d24d33607 100644 --- a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlConstructor.java +++ b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlConstructor.java @@ -9,25 +9,54 @@ import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Tag; +/** + * YamlConstructor 类继承自 SafeConstructor,用于自定义 YAML 构造过程。 + */ public class YamlConstructor extends SafeConstructor { + /** + * 构造函数,初始化 YamlConstructor 实例。 + * + * @param loaderOptions YAML 加载选项 + */ public YamlConstructor(LoaderOptions loaderOptions) { super(loaderOptions); this.yamlConstructors.put(Tag.MAP, new ConstructCustomObject()); } + /** + * 重写 flattenMapping 方法,用于扁平化映射节点。 + * + * @param node 要扁平化的映射节点 + */ @Override public void flattenMapping(@NotNull final MappingNode node) { super.flattenMapping(node); } + /** + * 构造给定节点的对象。 + * + * @param node 要构造的节点 + * @return 构造的对象,可能为 null + */ @Nullable public Object construct(@NotNull Node node) { return constructObject(node); } + /** + * ConstructCustomObject 内部类,用于自定义对象的构造。 + */ class ConstructCustomObject extends ConstructYamlMap { + /** + * 构造给定节点的对象。 + * + * @param node 要构造的节点 + * @return 构造的对象,可能为 null + * @throws YAMLException 如果遇到意外的引用映射结构 + */ @Nullable @Override public Object construct(@NotNull Node node) { @@ -37,6 +66,13 @@ public Object construct(@NotNull Node node) { return super.construct(node); } + /** + * 执行对象构造的第二步。 + * + * @param node 要构造的节点 + * @param object 已构造的对象 + * @throws YAMLException 如果遇到意外的引用映射结构 + */ @Override public void construct2ndStep(@NotNull Node node, @NotNull Object object) { throw new YAMLException("Unexpected referential mapping structure. Node: " + node); diff --git a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlRepresenter.java b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlRepresenter.java index 7076e5af6..e67ae224b 100644 --- a/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlRepresenter.java +++ b/module/basic/basic-configuration/src/main/java/taboolib/library/configuration/YamlRepresenter.java @@ -4,15 +4,32 @@ import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.representer.Representer; +/** + * YamlRepresenter 类扩展了 Representer 类,用于自定义 YAML 表示。 + */ public class YamlRepresenter extends Representer { + /** + * 构造函数,初始化 YamlRepresenter。 + * + * @param options DumperOptions 实例,用于配置 YAML 转储选项。 + */ public YamlRepresenter(DumperOptions options) { super(options); this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection()); } + /** + * 内部类 RepresentConfigurationSection,用于表示 ConfigurationSection 对象。 + */ class RepresentConfigurationSection extends RepresentMap { + /** + * 重写 representData 方法,用于将 ConfigurationSection 对象转换为 YAML 节点。 + * + * @param data 要表示的对象,应为 ConfigurationSection 类型。 + * @return 表示给定数据的 YAML 节点。 + */ @Override public Node representData(Object data) { return super.representData(((ConfigurationSection) data).getValues(false)); diff --git a/module/basic/basic-configuration/src/main/java/taboolib/module/configuration/YamlCommentLoader.java b/module/basic/basic-configuration/src/main/java/taboolib/module/configuration/YamlCommentLoader.java index bfc1238cf..36125aa2b 100644 --- a/module/basic/basic-configuration/src/main/java/taboolib/module/configuration/YamlCommentLoader.java +++ b/module/basic/basic-configuration/src/main/java/taboolib/module/configuration/YamlCommentLoader.java @@ -16,11 +16,10 @@ import java.util.Map; /** - * TabooLib - * taboolib.module.configuration.YamlCommentLoader - * - * @author 坏黑 - * @since 2022/10/3 01:13 + * YAML 注释加载器 + *

+ * 该类负责处理 YAML 文件中的注释,包括加载、调整和保存注释。 + * 它使用 SnakeYAML 库来解析和操作 YAML 结构。 */ @SuppressWarnings("FieldCanBeLocal") public class YamlCommentLoader { diff --git a/module/basic/basic-configuration/src/main/kotlin/taboolib/library/configuration/ConfigurationSection.kt b/module/basic/basic-configuration/src/main/kotlin/taboolib/library/configuration/ConfigurationSection.kt index a0dd0871b..b8406de7c 100644 --- a/module/basic/basic-configuration/src/main/kotlin/taboolib/library/configuration/ConfigurationSection.kt +++ b/module/basic/basic-configuration/src/main/kotlin/taboolib/library/configuration/ConfigurationSection.kt @@ -7,530 +7,500 @@ import taboolib.module.configuration.Type */ interface ConfigurationSection { - /** 原始类型 */ + /** 原始配置对象 */ val primitiveConfig: Any /** 父节点 */ val parent: ConfigurationSection? - /** 节点名 */ + /** 节点名称 */ val name: String /** 节点类型 */ val type: Type /** - * Gets a set containing all keys in this section. + * 获取此节点中所有键的集合。 * + * 如果 deep 设置为 true,则将包含任何子 [ConfigurationSection] 中的所有键 + * (以及它们的子键等)。这些键将以有效的路径表示法呈现,以供使用。 * - * If deep is set to true, then this will contain all the keys within any - * child [ConfigurationSection]s (and their children, etc). These - * will be in a valid path notation for you to use. + * 如果 deep 设置为 false,则只包含直接子节点的键,而不包括它们自己的子节点。 * - * - * If deep is set to false, then this will contain only the keys of any - * direct children, and not their own children. - * - * @param deep Whether or not to get a deep list, as opposed to a shallow - * list. - * @return Set of keys contained within this ConfigurationSection. + * @param deep 是否获取深层列表,而不是浅层列表。 + * @return 包含在此 ConfigurationSection 中的键集合。 */ fun getKeys(deep: Boolean): Set /** - * Checks if this [ConfigurationSection] contains the given path. - * + * 检查此 [ConfigurationSection] 是否包含给定路径。 * - * If the value for the requested path does not exist but a default value - * has been specified, this will return true. + * 如果请求路径的值不存在但已指定默认值,则此方法将返回 true。 * - * @param path Path to check for existence. - * @return True if this section contains the requested path, either via - * default or being set. - * @throws IllegalArgumentException Thrown when path is null. + * @param path 要检查存在性的路径。 + * @return 如果此节点包含请求的路径(通过默认值或已设置),则返回 true。 + * @throws IllegalArgumentException 当路径为 null 时抛出。 */ operator fun contains(path: String): Boolean /** - * Gets the requested Object by path. + * 通过路径获取请求的对象。 * + * 如果对象不存在但已指定默认值,则将返回默认值。 + * 如果对象不存在且未指定默认值,则返回 null。 * - * If the Object does not exist but a default value has been specified, - * this will return the default value. If the Object does not exist and no - * default value was specified, this will return null. - * - * @param path Path of the Object to get. - * @return Requested Object. + * @param path 要获取的对象的路径。 + * @return 请求的对象。 */ operator fun get(path: String): Any? /** - * Gets the requested Object by path, returning a default value if not - * found. + * 通过路径获取请求的对象,如果未找到则返回默认值。 * - * @param path Path of the Object to get. - * @param def The default value to return if the path is not found. - * @return Requested Object. + * @param path 要获取的对象的路径。 + * @param def 如果未找到路径时要返回的默认值。 + * @return 请求的对象。 */ operator fun get(path: String, def: Any?): Any? /** - * Sets the specified path to the given value. - * + * 将指定路径设置为给定值。 * - * If value is null, the entry will be removed. Any existing entry will be - * replaced, regardless of what the new value is. + * 如果值为 null,则会删除该条目。任何现有条目都将被替换,无论新值是什么。 * - * @param path Path of the object to set. - * @param value New value to set the path to. + * @param path 要设置的对象的路径。 + * @param value 要设置的新值。 */ operator fun set(path: String, value: Any?) /** - * Gets the requested String by path. + * 通过路径获取请求的字符串。 * + * 如果字符串不存在但已指定默认值,则将返回默认值。 + * 如果字符串不存在且未指定默认值,则返回 null。 * - * If the String does not exist but a default value has been specified, - * this will return the default value. If the String does not exist and no - * default value was specified, this will return null. - * - * @param path Path of the String to get. - * @return Requested String. + * @param path 要获取的字符串的路径。 + * @return 请求的字符串。 */ fun getString(path: String): String? /** - * Gets the requested String by path, returning a default value if not - * found. + * 通过路径获取请求的字符串,如果未找到则返回默认值。 * - * @param path Path of the String to get. - * @param def The default value to return if the path is not found or is - * not a String. - * @return Requested String. + * @param path 要获取的字符串的路径。 + * @param def 如果未找到路径或不是字符串时要返回的默认值。 + * @return 请求的字符串。 */ fun getString(path: String, def: String?): String? /** - * Checks if the specified path is a String. - * + * 检查指定路径是否为字符串。 * - * If the path exists but is not a String, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a String and return appropriately. + * 如果路径存在但不是字符串,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为字符串并相应返回。 * - * @param path Path of the String to check. - * @return Whether or not the specified path is a String. + * @param path 要检查的字符串的路径。 + * @return 指定路径是否为字符串。 */ fun isString(path: String): Boolean /** - * Gets the requested int by path. + * 通过路径获取请求的整数。 * + * 如果整数不存在但已指定默认值,则将返回默认值。 + * 如果整数不存在且未指定默认值,则返回 0。 * - * If the int does not exist but a default value has been specified, this - * will return the default value. If the int does not exist and no default - * value was specified, this will return 0. - * - * @param path Path of the int to get. - * @return Requested int. + * @param path 要获取的整数的路径。 + * @return 请求的整数。 */ fun getInt(path: String): Int /** - * Gets the requested int by path, returning a default value if not found. + * 通过路径获取请求的整数,如果未找到则返回默认值。 * - * @param path Path of the int to get. - * @param def The default value to return if the path is not found or is - * not an int. - * @return Requested int. + * @param path 要获取的整数的路径。 + * @param def 如果未找到路径或不是整数时要返回的默认值。 + * @return 请求的整数。 */ fun getInt(path: String, def: Int): Int /** - * Checks if the specified path is an int. - * + * 检查指定路径是否为整数。 * - * If the path exists but is not a int, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a int and return appropriately. + * 如果路径存在但不是整数,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为整数并相应返回。 * - * @param path Path of the int to check. - * @return Whether or not the specified path is an int. + * @param path 要检查的整数的路径。 + * @return 指定路径是否为整数。 */ fun isInt(path: String): Boolean /** - * Gets the requested boolean by path. + * 通过路径获取请求的布尔值。 * + * 如果布尔值不存在但已指定默认值,则将返回默认值。 + * 如果布尔值不存在且未指定默认值,则返回 false。 * - * If the boolean does not exist but a default value has been specified, - * this will return the default value. If the boolean does not exist and - * no default value was specified, this will return false. - * - * @param path Path of the boolean to get. - * @return Requested boolean. + * @param path 要获取的布尔值的路径。 + * @return 请求的布尔值。 */ fun getBoolean(path: String): Boolean /** - * Gets the requested boolean by path, returning a default value if not - * found. + * 通过路径获取请求的布尔值,如果未找到则返回默认值。 * - * @param path Path of the boolean to get. - * @param def The default value to return if the path is not found or is - * not a boolean. - * @return Requested boolean. + * @param path 要获取的布尔值的路径。 + * @param def 如果未找到路径或不是布尔值时要返回的默认值。 + * @return 请求的布尔值。 */ fun getBoolean(path: String, def: Boolean): Boolean /** - * Checks if the specified path is a boolean. - * + * 检查指定路径是否为布尔值。 * - * If the path exists but is not a boolean, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a boolean and return appropriately. + * 如果路径存在但不是布尔值,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为布尔值并相应返回。 * - * @param path Path of the boolean to check. - * @return Whether or not the specified path is a boolean. + * @param path 要检查的布尔值的路径。 + * @return 指定路径是否为布尔值。 */ fun isBoolean(path: String): Boolean /** - * Gets the requested double by path. - * + * 通过路径获取请求的双精度浮点数。 * - * If the double does not exist but a default value has been specified, - * this will return the default value. If the double does not exist and no - * default value was specified, this will return 0. + * 如果双精度浮点数不存在但已指定默认值,则将返回默认值。 + * 如果双精度浮点数不存在且未指定默认值,则返回 0。 * - * @param path Path of the double to get. - * @return Requested double. + * @param path 要获取的双精度浮点数的路径。 + * @return 请求的双精度浮点数。 */ fun getDouble(path: String): Double /** - * Gets the requested double by path, returning a default value if not - * found. + * 通过路径获取请求的双精度浮点数,如果未找到则返回默认值。 * - * @param path Path of the double to get. - * @param def The default value to return if the path is not found or is - * not a double. - * @return Requested double. + * @param path 要获取的双精度浮点数的路径。 + * @param def 如果未找到路径或不是双精度浮点数时要返回的默认值。 + * @return 请求的双精度浮点数。 */ fun getDouble(path: String, def: Double): Double /** - * Checks if the specified path is a double. + * 检查指定路径是否为双精度浮点数。 * + * 如果路径存在但不是双精度浮点数,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为双精度浮点数并相应返回。 * - * If the path exists but is not a double, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a double and return appropriately. - * - * @param path Path of the double to check. - * @return Whether or not the specified path is a double. + * @param path 要检查的双精度浮点数的路径。 + * @return 指定路径是否为双精度浮点数。 */ fun isDouble(path: String): Boolean /** - * Gets the requested long by path. - * + * 通过路径获取请求的长整数。 * - * If the long does not exist but a default value has been specified, this - * will return the default value. If the long does not exist and no - * default value was specified, this will return 0. + * 如果长整数不存在但已指定默认值,则将返回默认值。 + * 如果长整数不存在且未指定默认值,则返回 0。 * - * @param path Path of the long to get. - * @return Requested long. + * @param path 要获取的长整数的路径。 + * @return 请求的长整数。 */ fun getLong(path: String): Long /** - * Gets the requested long by path, returning a default value if not - * found. + * 通过路径获取请求的长整数,如果未找到则返回默认值。 * - * @param path Path of the long to get. - * @param def The default value to return if the path is not found or is - * not a long. - * @return Requested long. + * @param path 要获取的长整数的路径。 + * @param def 如果未找到路径或不是长整数时要返回的默认值。 + * @return 请求的长整数。 */ fun getLong(path: String, def: Long): Long /** - * Checks if the specified path is a long. + * 检查指定路径是否为长整数。 * + * 如果路径存在但不是长整数,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为长整数并相应返回。 * - * If the path exists but is not a long, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a long and return appropriately. - * - * @param path Path of the long to check. - * @return Whether or not the specified path is a long. + * @param path 要检查的长整数的路径。 + * @return 指定路径是否为长整数。 */ fun isLong(path: String): Boolean /** - * Gets the requested List by path. - * + * 通过路径获取请求的列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return null. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回 null。 * - * @param path Path of the List to get. - * @return Requested List. + * @param path 要获取的列表的路径。 + * @return 请求的列表。 */ fun getList(path: String): List<*>? /** - * Gets the requested List by path, returning a default value if not - * found. + * 通过路径获取请求的列表,如果未找到则返回默认值。 * - * @param path Path of the List to get. - * @param def The default value to return if the path is not found or is - * not a List. - * @return Requested List. + * @param path 要获取的列表的路径。 + * @param def 如果未找到路径或不是列表时要返回的默认值。 + * @return 请求的列表。 */ fun getList(path: String, def: List<*>?): List<*>? /** - * Checks if the specified path is a List. + * 检查指定路径是否为列表。 * + * 如果路径存在但不是列表,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为列表并相应返回。 * - * If the path exists but is not a List, this will return false. If the - * path does not exist, this will return false. If the path does not exist - * but a default value has been specified, this will check if that default - * value is a List and return appropriately. - * - * @param path Path of the List to check. - * @return Whether or not the specified path is a List. + * @param path 要检查的列表的路径。 + * @return 指定路径是否为列表。 */ fun isList(path: String): Boolean /** - * Gets the requested List of String by path. - * + * 通过路径获取请求的字符串列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * + * 此方法将尝试将任何值转换为字符串(如果可能),但如果不兼容,可能会遗漏一些值。 * - * This method will attempt to cast any values into a String if possible, - * but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of String. + * @param path 要获取的列表的路径。 + * @return 请求的字符串列表。 */ fun getStringList(path: String): List /** - * Gets the requested List of Integer by path. - * - * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 通过路径获取请求的整数列表。 * + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * - * This method will attempt to cast any values into a Integer if possible, - * but may miss any values out if they are not compatible. + * 此方法将尝试将任何值转换为整数(如果可能),但如果不兼容,可能会遗漏一些值。 * - * @param path Path of the List to get. - * @return Requested List of Integer. + * @param path 要获取的列表的路径。 + * @return 请求的整数列表。 */ fun getIntegerList(path: String): List /** - * Gets the requested List of Boolean by path. - * - * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 通过路径获取请求的布尔值列表。 * + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * - * This method will attempt to cast any values into a Boolean if possible, - * but may miss any values out if they are not compatible. + * 此方法将尝试将任何值转换为布尔值(如果可能),但如果不兼容,可能会遗漏一些值。 * - * @param path Path of the List to get. - * @return Requested List of Boolean. + * @param path 要获取的列表的路径。 + * @return 请求的布尔值列表。 */ fun getBooleanList(path: String): List /** - * Gets the requested List of Double by path. + * 通过路径获取请求的双精度浮点数列表。 * + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 此方法将尝试将任何值转换为双精度浮点数(如果可能),但如果不兼容,可能会遗漏一些值。 * - * - * This method will attempt to cast any values into a Double if possible, - * but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Double. + * @param path 要获取的列表的路径。 + * @return 请求的双精度浮点数列表。 */ fun getDoubleList(path: String): List /** - * Gets the requested List of Float by path. - * - * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 通过路径获取请求的浮点数列表。 * + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * - * This method will attempt to cast any values into a Float if possible, - * but may miss any values out if they are not compatible. + * 此方法将尝试将任何值转换为浮点数(如果可能),但如果不兼容,可能会遗漏一些值。 * - * @param path Path of the List to get. - * @return Requested List of Float. + * @param path 要获取的列表的路径。 + * @return 请求的浮点数列表。 */ fun getFloatList(path: String): List /** - * Gets the requested List of Long by path. + * 通过路径获取请求的长整数列表。 * + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 此方法将尝试将任何值转换为长整数(如果可能),但如果不兼容,可能会遗漏一些值。 * - * - * This method will attempt to cast any values into a Long if possible, - * but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Long. + * @param path 要获取的列表的路径。 + * @return 请求的长整数列表。 */ fun getLongList(path: String): List /** - * Gets the requested List of Byte by path. - * + * 通过路径获取请求的字节列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * + * 此方法将尝试将任何值转换为字节(如果可能),但如果不兼容,可能会遗漏一些值。 * - * This method will attempt to cast any values into a Byte if possible, - * but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Byte. + * @param path 要获取的列表的路径。 + * @return 请求的字节列表。 */ fun getByteList(path: String): List /** - * Gets the requested List of Character by path. - * + * 通过路径获取请求的字符列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * + * 此方法将尝试将任何值转换为字符(如果可能),但如果不兼容,可能会遗漏一些值。 * - * This method will attempt to cast any values into a Character if - * possible, but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Character. + * @param path 要获取的列表的路径。 + * @return 请求的字符列表。 */ fun getCharacterList(path: String): List /** - * Gets the requested List of Short by path. - * + * 通过路径获取请求的短整数列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * + * 此方法将尝试将任何值转换为短整数(如果可能),但如果不兼容,可能会遗漏一些值。 * - * This method will attempt to cast any values into a Short if possible, - * but may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Short. + * @param path 要获取的列表的路径。 + * @return 请求的短整数列表。 */ fun getShortList(path: String): List /** - * Gets the requested List of Maps by path. - * + * 通过路径获取请求的映射列表。 * - * If the List does not exist but a default value has been specified, this - * will return the default value. If the List does not exist and no - * default value was specified, this will return an empty List. + * 如果列表不存在但已指定默认值,则将返回默认值。 + * 如果列表不存在且未指定默认值,则返回空列表。 * + * 此方法将尝试将任何值转换为映射(如果可能),但如果不兼容,可能会遗漏一些值。 * - * This method will attempt to cast any values into a Map if possible, but - * may miss any values out if they are not compatible. - * - * @param path Path of the List to get. - * @return Requested List of Maps. + * @param path 要获取的列表的路径。 + * @return 请求的映射列表。 */ fun getMapList(path: String): List> /** - * Gets the requested ConfigurationSection by path. - * + * 通过路径获取请求的 ConfigurationSection。 * - * If the ConfigurationSection does not exist but a default value has been - * specified, this will return the default value. If the - * ConfigurationSection does not exist and no default value was specified, - * this will return null. + * 如果 ConfigurationSection 不存在但已指定默认值,则将返回默认值。 + * 如果 ConfigurationSection 不存在且未指定默认值,则返回 null。 * - * @param path Path of the ConfigurationSection to get. - * @return Requested ConfigurationSection. + * @param path 要获取的 ConfigurationSection 的路径。 + * @return 请求的 ConfigurationSection。 */ fun getConfigurationSection(path: String): ConfigurationSection? /** - * Checks if the specified path is a ConfigurationSection. - * + * 检查指定路径是否为 ConfigurationSection。 * - * If the path exists but is not a ConfigurationSection, this will return - * false. If the path does not exist, this will return false. If the path - * does not exist but a default value has been specified, this will check - * if that default value is a ConfigurationSection and return - * appropriately. + * 如果路径存在但不是 ConfigurationSection,则返回 false。 + * 如果路径不存在,则返回 false。 + * 如果路径不存在但已指定默认值,则检查该默认值是否为 ConfigurationSection 并相应返回。 * - * @param path Path of the ConfigurationSection to check. - * @return Whether or not the specified path is a ConfigurationSection. + * @param path 要检查的 ConfigurationSection 的路径。 + * @return 指定路径是否为 ConfigurationSection。 */ fun isConfigurationSection(path: String): Boolean + /** + * 通过路径获取请求的枚举值。 + * + * @param path 要获取的枚举值的路径。 + * @param type 枚举类的 Class 对象。 + * @return 请求的枚举值,如果未找到则返回 null。 + */ fun > getEnum(path: String, type: Class): T? + /** + * 通过路径获取请求的枚举值列表。 + * + * @param path 要获取的枚举值列表的路径。 + * @param type 枚举类的 Class 对象。 + * @return 请求的枚举值列表。 + */ fun > getEnumList(path: String, type: Class): List + /** + * 在指定路径创建一个新的 ConfigurationSection。 + * + * @param path 要创建的 ConfigurationSection 的路径。 + * @return 新创建的 ConfigurationSection。 + */ fun createSection(path: String): ConfigurationSection + /** + * 将当前 ConfigurationSection 转换为 Map。 + * + * @return 包含当前 ConfigurationSection 所有键值对的 Map。 + */ fun toMap(): Map + /** + * 获取指定路径的注释。 + * + * @param path 要获取注释的路径。 + * @return 指定路径的注释,如果没有注释则返回 null。 + */ fun getComment(path: String): String? + /** + * 获取指定路径的注释列表。 + * + * @param path 要获取注释的路径。 + * @return 指定路径的注释列表。 + */ fun getComments(path: String): List + /** + * 设置指定路径的注释。 + * + * @param path 要设置注释的路径。 + * @param comment 要设置的注释,如果为 null 则删除现有注释。 + */ fun setComment(path: String, comment: String?) + /** + * 设置指定路径的注释列表。 + * + * @param path 要设置注释的路径。 + * @param comments 要设置的注释列表。 + */ fun setComments(path: String, comments: List) + /** + * 向指定路径添加注释。 + * + * @param path 要添加注释的路径。 + * @param comments 要添加的注释列表。 + */ fun addComments(path: String, comments: List) + /** + * 获取当前 ConfigurationSection 的所有值。 + * + * @param deep 是否包含子节点的值。 + * @return 包含所有值的 Map。 + */ fun getValues(deep: Boolean): Map + /** + * 清除当前 ConfigurationSection 中的所有值。 + */ fun clear() } \ No newline at end of file diff --git a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/ConfigFile.kt b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/ConfigFile.kt index a52b1a22c..8baf3aa6d 100644 --- a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/ConfigFile.kt +++ b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/ConfigFile.kt @@ -18,25 +18,56 @@ import java.text.SimpleDateFormat * @author mac * @since 2021/11/22 12:49 上午 */ +/** + * 表示一个配置文件,继承自 ConfigSection 并实现 Configuration 接口。 + * + * @property file 与此配置关联的文件对象,可为 null + * @property name 配置文件的名称(不包含扩展名) + * + * @param root 根配置对象 + */ open class ConfigFile(root: Config) : ConfigSection(root), Configuration { override var file: File? = null override var name: String = "" + // 存储重载回调的列表 private val reloadCallback = ArrayList() + /** + * 添加一个在配置重载时执行的回调。 + * + * @param runnable 要执行的回调 + */ override fun onReload(runnable: Runnable) { reloadCallback.add(runnable) } + /** + * 将配置保存为字符串。 + * + * @return 表示配置内容的字符串 + */ override fun saveToString(): String { return toString() } + /** + * 将配置保存到指定文件。 + * + * @param file 要保存到的文件,如果为 null 则使用当前关联的文件 + * @throws IllegalStateException 如果未指定文件 + */ override fun saveToFile(file: File?) { (file ?: this.file)?.writeText(saveToString()) ?: error("File not specified") } + /** + * 从指定文件加载配置。 + * + * @param file 要加载的文件 + * @throws Exception 如果加载过程中发生错误 + */ override fun loadFromFile(file: File) { this.file = file this.name = file.nameWithoutExtension @@ -44,6 +75,7 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration { clear() parser().parse(file, root, ParsingMode.REPLACE, FileNotFoundAction.THROW_ERROR) } catch (ex: Exception) { + // 如果加载失败且文件扩展名不是 .bak,则创建备份 if (file.extension != "bak") { file.copyTo(File(file.parent, file.name + "_" + SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis()) + ".bak")) } @@ -53,6 +85,12 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration { reloadCallback.forEach { it.run() } } + /** + * 从字符串加载配置。 + * + * @param contents 包含配置内容的字符串 + * @throws Exception 如果解析过程中发生错误 + */ override fun loadFromString(contents: String) { try { clear() @@ -64,22 +102,40 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration { reloadCallback.forEach { it.run() } } + /** + * 从 Reader 加载配置。 + * + * @param reader 用于读取配置的 Reader 对象 + */ override fun loadFromReader(reader: Reader) { clear() parser().parse(reader, root, ParsingMode.REPLACE) reloadCallback.forEach { it.run() } } + /** + * 从 InputStream 加载配置。 + * + * @param inputStream 用于读取配置的 InputStream 对象 + */ override fun loadFromInputStream(inputStream: InputStream) { clear() parser().parse(inputStream, root, ParsingMode.REPLACE) reloadCallback.forEach { it.run() } } + /** + * 重新加载配置。 + */ override fun reload() { loadFromFile(file ?: return) } + /** + * 更改配置的类型。 + * + * @param type 新的配置类型 + */ override fun changeType(type: Type) { val format = type.newFormat() fun process(value: Any) { @@ -95,6 +151,11 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration { process(root) } + /** + * 获取与当前配置格式相关联的解析器。 + * + * @return 配置解析器 + */ private fun parser(): ConfigParser { return root.configFormat().createParser() } diff --git a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/Converters.kt b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/Converters.kt index 6f488a7ed..8888069c8 100644 --- a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/Converters.kt +++ b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/Converters.kt @@ -4,6 +4,12 @@ import com.electronwill.nightconfig.core.UnmodifiableConfig import com.electronwill.nightconfig.core.conversion.Converter import java.util.* +/** + * 用于在 Map 和 UnmodifiableConfig 之间进行转换的转换器。 + * + * @property Map<*, *> 源映射类型 + * @property UnmodifiableConfig 目标配置类型 + */ class MapConverter : Converter, UnmodifiableConfig> { override fun convertToField(config: UnmodifiableConfig): Map<*, *> { @@ -15,6 +21,12 @@ class MapConverter : Converter, UnmodifiableConfig> { } } +/** + * 用于在 UUID 和 String 之间进行转换的转换器。 + * + * @property UUID 源 UUID 类型 + * @property String 目标字符串类型 + */ class UUIDConverter : Converter { override fun convertToField(value: String): UUID { diff --git a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlParser.kt b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlParser.kt index 1aba86b89..8be2ddf2d 100644 --- a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlParser.kt +++ b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlParser.kt @@ -20,7 +20,9 @@ import java.io.Reader import java.nio.charset.StandardCharsets /** - * @author 坏黑 + * YAML 解析器类,实现了 ConfigParser 接口 + * + * @property configFormat 配置格式 */ class YamlParser(val configFormat: ConfigFormat) : ConfigParser { @@ -59,6 +61,12 @@ class YamlParser(val configFormat: ConfigFormat) : ConfigParser } } + /** + * 从字符串加载 YAML 内容 + * + * @param contents YAML 内容字符串 + * @param section 目标配置部分 + */ fun loadFromString(contents: String, section: ConfigurationSection) { if (contents.isEmpty()) { return diff --git a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlWriter.kt b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlWriter.kt index 8873d6625..96f2d266b 100644 --- a/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlWriter.kt +++ b/module/basic/basic-configuration/src/main/kotlin/taboolib/module/configuration/YamlWriter.kt @@ -14,7 +14,7 @@ import java.io.StringWriter import java.io.Writer /** - * @author 坏黑 + * YAML 写入器类,实现了 ConfigWriter 接口 */ class YamlWriter : ConfigWriter { diff --git a/module/bukkit/bukkit-util/src/main/kotlin/taboolib/platform/util/BukkitSkull.kt b/module/bukkit/bukkit-util/src/main/kotlin/taboolib/platform/util/BukkitSkull.kt new file mode 100644 index 000000000..4b4f1cc53 --- /dev/null +++ b/module/bukkit/bukkit-util/src/main/kotlin/taboolib/platform/util/BukkitSkull.kt @@ -0,0 +1,181 @@ +@file:Suppress("DEPRECATION", "HttpUrlsUsage", "MemberVisibilityCanBePrivate") + +package taboolib.platform.util + +import com.google.gson.JsonParser +import com.mojang.authlib.GameProfile +import com.mojang.authlib.properties.Property +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.SkullMeta +import org.tabooproject.reflex.Reflex.Companion.getProperty +import org.tabooproject.reflex.Reflex.Companion.setProperty +import taboolib.common5.util.decodeBase64 +import taboolib.library.xseries.XMaterial +import java.net.MalformedURLException +import java.net.URL +import java.util.* +import java.util.function.Function + +/** + * ExamplePlugin + * taboolib.platform.util.Heads + * + * @author mical + * @since 2024/9/17 10:29 + */ +object BukkitSkull { + + /** 旧版本 Gson 的 JsonParser 没有 parseString 静态方法, 要使用这个 */ + private val JSON_PARSER = JsonParser() + + /** 默认头颅 */ + private val DEFAULT_HEAD = XMaterial.PLAYER_HEAD.parseItem()!! + + /** 是否使用 1.13 及以上版本 (扁平化后) */ + private val use13 = kotlin.runCatching { Material.PLAYER_HEAD }.isSuccess + + /** 获取 Property 值的方法,兼容高低不同版本 */ + private val getProfileMethod = try { + Property::class.java.getDeclaredMethod("value") + } catch (_: Throwable) { + Property::class.java.getDeclaredMethod("getValue") + } + + /** + * 应用头颅纹理到物品上 (无自定义处理函数版本) + * @param item 要应用纹理的物品, 会对源物品进行修改, 如果为 null 或 AIR 则由我们创建一个物品 + * @param headBase64 头颅纹理的 Base64 编码或玩家名称 + * @return 应用了纹理的物品 + */ + fun applySkull(item: ItemStack, headBase64: String): ItemStack { + return applySkull(item, headBase64, null) + } + + /** + * 不传入头颅源物品, 通过给定头颅纹理 Base64 或玩家名称来构建一个新头 + * @param headBase64 头颅纹理的 Base64 编码或玩家名称 + * @param func 自定义处理函数, 若为 null 或返回一个为 null 的 ItemStack 则尝试通过原版方法应用玩家纹理 + * @return 应用了纹理的物品 + */ + fun applySkull(headBase64: String, func: Function?): ItemStack { + return applySkull(DEFAULT_HEAD.clone(), headBase64, func) + } + + /** + * 不传入头颅源物品, 通过给定头颅纹理 Base64 或玩家名称来构建一个新头 + * @param headBase64 头颅纹理的 Base64 编码或玩家名称 + * @return 应用了纹理的物品 + */ + fun applySkull(headBase64: String): ItemStack { + return applySkull(DEFAULT_HEAD.clone(), headBase64, null) + } + + /** + * 应用头颅纹理到物品上 + * 当输入为玩家名称时, 可以传入来调用第三方插件处理头, 例如 SkinsRestorer, 适用于离线服务器等情况 + * @param item 要应用纹理的物品, 会对源物品进行修改, 如果为 null 或 AIR 则由我们创建一个物品 + * @param headBase64 头颅纹理的 Base64 编码或玩家名称 + * @param func 自定义处理函数, 若为 null 或返回一个为 null 的 ItemStack 则尝试通过原版方法应用玩家纹理 + * @return 应用了纹理的物品 + */ + fun applySkull(item: ItemStack, headBase64: String, func: Function?): ItemStack { + val meta = item.itemMeta as? SkullMeta ?: return item + // 扁平化前要确保头颅类型为玩家头 + // 扁平化前「玩家头」的子 ID 为 3 + if (!use13) { + item.durability = 3 + } + // 判定传入为玩家名 + if (headBase64.length <= 20) { + // 如果应用了处理函数, 则尝试通过自定义函数处理物品 + if (func != null) { + val result = func.apply(item) + // 尝试返回自定义函数中返回的物品 + if (result != null) { + return result + } + } + // 如果没有自定义处理函数, 或自定义函数处理结果为空 (一般是开发者认为现有 API 因为种种原因无法处理) + try { + // 不清楚从哪个版本开始可以设置 OwningPlayer + meta.owningPlayer = Bukkit.getOfflinePlayer(headBase64) + } catch (_: Throwable) { + meta.owner = headBase64 + } + item.itemMeta = meta + return item + } + // 下面这是 Spigot 1.18.1 发布之后添加的头颅工具, 准确来说从 1.18.2 开始 + try { + val profile = Bukkit.createPlayerProfile(UUID(0, 0), "TabooLib") + val textures = profile.textures + // NOTICE 下面这一行代码我不太清楚是如何工作的, 但是它工作正常. 来自 TrMenu + val texture = if (headBase64.length in 60..100) encodeTexture(headBase64) else headBase64 + val url = URL(getTextureURLFromBase64(texture)) + try { + textures.skin = url + } catch (e: MalformedURLException) { + throw IllegalStateException("Invalid skull base64 content", e) + } + meta.ownerProfile = profile + } catch (_: Throwable) { + // 如果使用 1.18.1 及以下版本, 则使用老方法处理 + val profile = GameProfile(UUID(0, 0), "TabooLib") + val texture = if (headBase64.length in 60..100) encodeTexture(headBase64) else headBase64 + profile.properties.put("textures", Property("textures", texture, "TabooLib_TexturedSkull")) + + meta.setProperty("profile", profile) + } + item.itemMeta = meta + return item + } + + /** + * 获取头颅的纹理字符串, 也就是 Base64 文本 + * @param meta 头颅物品的元数据 + * @return 纹理字符串,如果没有则返回空字符串 + */ + fun getSkullValue(meta: SkullMeta): String { + val profile = meta.getProperty("profile") ?: return "" + val properties = profile.properties["textures"] ?: return "" + if (properties.isEmpty()) return "" + + for (property in properties) { + val value: String = try { + getProfileMethod.invoke(property) as String + } catch (_: Throwable) { + continue + } + if (value.isNotEmpty()) return value + } + return "" + } + + /** + * 将纹理 ID 编码为完整的纹理 Base64 字符串 + * @param input 纹理 ID + * @return 编码后的 Base64 字符串 + */ + private fun encodeTexture(input: String): String { + return with(Base64.getEncoder()) { + encodeToString("{\"textures\":{\"SKIN\":{\"url\":\"http://textures.minecraft.net/texture/$input\"}}}".toByteArray()) + } + } + + /** + * 从 Base64 编码的纹理数据中提取纹理 URL + * @param headBase64 Base64 编码的纹理数据 + * @return 纹理 URL + */ + private fun getTextureURLFromBase64(headBase64: String): String { + return JSON_PARSER + .parse(String(headBase64.decodeBase64())) + .asJsonObject + .getAsJsonObject("textures") + .getAsJsonObject("SKIN") + .get("url") + .asString + } +} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/build.gradle.kts b/module/bukkit/bukkit-xseries-item/build.gradle.kts deleted file mode 100644 index 6c6dd2111..000000000 --- a/module/bukkit/bukkit-xseries-item/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -@file:Suppress("GradlePackageUpdate", "VulnerableLibrariesLocal") - -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -dependencies { - compileOnly(project(":common")) - compileOnly(project(":common-platform-api")) - compileOnly(project(":common-util")) - compileOnly(project(":module:basic:basic-configuration")) - compileOnly(project(":module:bukkit:bukkit-util")) - compileOnly(project(":module:minecraft:minecraft-chat")) - // 本体 - compileOnly(project(":module:bukkit:bukkit-xseries")) - // 服务端 - compileOnly("ink.ptms.core:v12004:12004-minimize:mapped") - compileOnly("net.md-5:bungeecord-chat:1.20") - compileOnly("com.mojang:authlib:5.0.51") - // XSeries - compileOnly("com.google.code.findbugs:jsr305:3.0.2") - compileOnly("org.apache.logging.log4j:log4j-api:2.14.1") -} - -tasks.withType { - kotlinOptions { - jvmTarget = "17" - } -} - -configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_1_8 -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerProfiles.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerProfiles.java deleted file mode 100644 index cd178a6e6..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerProfiles.java +++ /dev/null @@ -1,220 +0,0 @@ -package taboolib.library.xseries.profiles; - -import taboolib.library.xseries.reflection.XReflection; -import com.google.common.collect.Iterables; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.authlib.properties.PropertyMap; -import org.bukkit.Bukkit; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; - -@ApiStatus.Internal -public final class PlayerProfiles { - /** - * In v1.20.2 there were some changes to the Mojang API. - * Before that version, both UUID and name fields couldn't be null, only one of them. - * It gave the error: {@code Name and ID cannot both be blank} - * Here, "blank" is null for UUID, and {@code Character.isWhitespace} for the name field. - */ - public static final String DEFAULT_PROFILE_NAME = "XSeries"; - - /** - * The signature value represents the version of XSeries library. - * It's not needed to change it every time, but it should be changed - * if the XSeries internals are changed. - */ - private static final Property XSERIES_GAMEPROFILE_SIGNATURE = new Property(DEFAULT_PROFILE_NAME, XReflection.XSERIES_VERSION); - private static final String TEXTURES_PROPERTY = "textures"; - - public static final GameProfile NIL = createGameProfile(PlayerUUIDs.IDENTITY_UUID, DEFAULT_PROFILE_NAME); - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - - /** - * Some people use this without quotes surrounding the keys, not sure if that'd work. - */ - public static final String TEXTURES_NBT_PROPERTY_PREFIX = "{\"textures\":{\"SKIN\":{\"url\":\""; - - /** - * The value after this URL is probably an SHA-252 value that Mojang uses to unique identify player skins. - *
- * This wiki documents how to - * get base64 information from player's UUID. - *

- * Older clients such as v1.8.9 (only tested for v1.8.9) cannot correctly load HTTPS textures. - * Not sure if plugins like ViaVersion handle this properly. - * Also, the {@link taboolib.library.xseries.profiles.mojang.MojangAPI} UUID_TO_PROFILE - * returns HTTP for texture URL when the Base64 is decoded, so we can keep it consistent - * when it's not explicitly defined by the user. - */ - public static final String TEXTURES_BASE_URL = "http://textures.minecraft.net/texture/"; - - public static Optional getTextureProperty(GameProfile profile) { - // This is the property with Base64 encoded value. - return Optional.ofNullable(Iterables.getFirst(profile.getProperties().get(TEXTURES_PROPERTY), null)); - } - - /** - * Retrieves the skin value from the given {@link GameProfile}. - * - * @param profile The {@link GameProfile} to retrieve the skin value from. - * @return The skin value as a {@link String}, or {@code null} if not found. - * @throws NullPointerException if {@code profile} is {@code null}. - */ - @Nullable - public static String getSkinValue(@Nonnull GameProfile profile) { - Objects.requireNonNull(profile, "Game profile cannot be null"); - return getTextureProperty(profile).map(PlayerProfiles::getPropertyValue).orElse(null); - } - - /** - * Retrieves the value of a {@link Property}, handling differences between versions. - * @since 4.0.1 - */ - public static String getPropertyValue(Property property) { - if (ProfilesCore.NULLABILITY_RECORD_UPDATE) return property.value(); - try { - return (String) ProfilesCore.Property_getValue.invoke(property); - } catch (Throwable throwable) { - throw new RuntimeException("Unable to get a property value: " + property, throwable); - } - } - - /** - * Checks if the provided {@link GameProfile} has a texture property. - * - * @param profile The {@link GameProfile} to check. - * @return {@code true} if the profile has a texture property, {@code false} otherwise. - */ - public static boolean hasTextures(GameProfile profile) { - return getTextureProperty(profile).isPresent(); - } - - /** - * Constructs a {@link GameProfile} using the provided texture hash and base64 string. - * - * @param hash The texture hash used to construct the profile's textures. - * @param base64 The base64 string representing the profile's textures. - * @return The constructed {@link GameProfile}. - * @implNote This method creates a {@link GameProfile} with a UUID derived from the provided hash - * to ensure consistency after restarts. - */ - @Nonnull - public static GameProfile profileFromHashAndBase64(String hash, String base64) { - java.util.UUID uuid = java.util.UUID.nameUUIDFromBytes(hash.getBytes(StandardCharsets.UTF_8)); - GameProfile profile = PlayerProfiles.createNamelessGameProfile(uuid); - PlayerProfiles.setTexturesProperty(profile, base64); - return profile; - } - - @SuppressWarnings("deprecation") - public static void removeTimestamp(GameProfile profile) { - JsonObject jsonObject = Optional.ofNullable(getSkinValue(profile)).map(PlayerProfiles::decodeBase64) - .map((decoded) -> new JsonParser().parse(decoded).getAsJsonObject()) - .orElse(null); - - if (jsonObject == null || !jsonObject.has("timestamp")) return; - jsonObject.remove("timestamp"); - - // Mojang's format is pretty-printed, so let's keep that. - setTexturesProperty(profile, encodeBase64(GSON.toJson(jsonObject))); - } - - /** - * Uses the online/offline UUID depending on {@link Bukkit#getOnlineMode()}. - * @return may return the same or a new profile. - * - * @param profile must have complete name and UUID - */ - public static GameProfile sanitizeProfile(GameProfile profile) { - // We could remove the unnecessary timestamp data, but let's keep it there, the texture is Base64 encoded anyway. - // It doesn't affect it in terms of performance. - // The timestamp property is the last time the values have been updated, this - // is instant in most cases, but are sometimes a few minutes? (or hours?) behind - // because of Mojang server's cache. - - // The stored cache UUID must be according to online/offline servers. - if (PlayerUUIDs.isOnlineMode()) return profile; - UUID offlineId = PlayerUUIDs.getOfflineUUID(profile.getName()); - PlayerUUIDs.ONLINE_TO_OFFLINE.put(profile.getId(), offlineId); - - GameProfile clone = createGameProfile(offlineId, profile.getName()); - clone.getProperties().putAll(profile.getProperties()); - return clone; - } - - public static GameProfile clone(GameProfile gameProfile) { - GameProfile clone = new GameProfile(gameProfile.getId(), gameProfile.getName()); - clone.getProperties().putAll(gameProfile.getProperties()); - return clone; - } - - public static void setTexturesProperty(GameProfile profile, String texture) { - Property property = new Property(TEXTURES_PROPERTY, texture); - PropertyMap properties = profile.getProperties(); - properties.asMap().remove(TEXTURES_PROPERTY); - properties.put(TEXTURES_PROPERTY, property); - } - - /** - * Encodes the provided string into Base64 format. - * - * @param str The string to encode. - * @return The Base64 encoded string. - */ - public static String encodeBase64(String str) { - return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Tries to decode the string as a Base64 value. - * - * @param base64 The Base64 string to decode. - * @return the decoded Base64 string if it is a valid Base64 string, or null if not. - */ - @Nullable - public static String decodeBase64(String base64) { - Objects.requireNonNull(base64, "Cannot decode null string"); - try { - byte[] bytes = Base64.getDecoder().decode(base64); - return new String(bytes, StandardCharsets.UTF_8); - } catch (IllegalArgumentException exception) { - return null; - } - } - - public static GameProfile createGameProfile(UUID uuid, String username) { - return signXSeries(new GameProfile(uuid, username)); - } - - /** - * All {@link GameProfile} created/modified by this library should have a special signature for debugging - * purposes, specially since we're directly messing with the server's internal cache - * it should be there in case something goes wrong. - */ - public static GameProfile signXSeries(GameProfile profile) { - // Just as an indicator that this is not a vanilla-created profile. - PropertyMap properties = profile.getProperties(); - // I don't think a single profile is being signed multiple times. - // Even if it was, it might be helpful? - // properties.asMap().remove(DEFAULT_PROFILE_NAME); // Remove previous versions if any. - properties.put(DEFAULT_PROFILE_NAME, XSERIES_GAMEPROFILE_SIGNATURE); - return profile; - } - - public static GameProfile createNamelessGameProfile(UUID id) { - return createGameProfile(id, DEFAULT_PROFILE_NAME); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerUUIDs.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerUUIDs.java deleted file mode 100644 index 9e70db37a..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/PlayerUUIDs.java +++ /dev/null @@ -1,136 +0,0 @@ -package taboolib.library.xseries.profiles; - -import taboolib.library.xseries.profiles.mojang.MojangAPI; -import com.google.common.base.Strings; -import org.bukkit.Bukkit; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@ApiStatus.Internal -public final class PlayerUUIDs { - /** - * Used as the default UUID for GameProfiles. - * Also used as a null-indicating value. - */ - public static final UUID IDENTITY_UUID = new UUID(0, 0); - - private static final Pattern UUID_NO_DASHES = Pattern.compile( - "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})" - ); - - /** - * We can't use Guava's BiMap here since non-existing players are cached too. - */ - public static final Map OFFLINE_TO_ONLINE = new HashMap<>(), ONLINE_TO_OFFLINE = new HashMap<>(); - public static final Map USERNAME_TO_ONLINE = new HashMap<>(); - - public static UUID UUIDFromDashlessString(String dashlessUUIDString) { - Matcher matcher = UUID_NO_DASHES.matcher(dashlessUUIDString); - try { - return UUID.fromString(matcher.replaceFirst("$1-$2-$3-$4-$5")); - } catch (IllegalArgumentException ex) { - throw new IllegalArgumentException("Cannot convert from dashless UUID: " + dashlessUUIDString, ex); - } - } - - public static String toUndashedUUID(UUID id) { - return id.toString().replace("-", ""); - } - - @Nonnull - public static UUID getOfflineUUID(@Nonnull String username) { - // Vanilla behavior across all platforms. - return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); - } - - public static boolean isOnlineMode() { - return Bukkit.getOnlineMode(); - } - - @Nullable - public static UUID getRealUUIDOfPlayer(@Nonnull String username) { - if (Strings.isNullOrEmpty(username)) - throw new IllegalArgumentException("Username is null or empty: " + username); - - UUID offlineUUID = getOfflineUUID(username); - UUID realUUID = USERNAME_TO_ONLINE.get(username); - boolean cached = realUUID != null; - if (realUUID == null) { - try { - realUUID = MojangAPI.requestUsernameToUUID(username); - if (realUUID == null) { - ProfileLogger.debug("Caching null for {} ({}) because it doesn't exist.", username, offlineUUID); - realUUID = IDENTITY_UUID; // Player not found, we should cache this information. - } else ONLINE_TO_OFFLINE.put(realUUID, offlineUUID); - OFFLINE_TO_ONLINE.put(offlineUUID, realUUID); - USERNAME_TO_ONLINE.put(username, realUUID); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - if (realUUID == IDENTITY_UUID) { - ProfileLogger.debug("Providing null UUID for {} because it doesn't exist.", username); - realUUID = null; - } else { - ProfileLogger.debug((cached ? "Cached " : "") + "Real UUID for {} ({}) is {}", username, offlineUUID, realUUID); - } - - return realUUID; - } - - /** - * @return null if a player with this username doesn't exist. - */ - @Nullable - public static UUID getRealUUIDOfPlayer(@Nonnull String username, @Nonnull UUID uuid) { - Objects.requireNonNull(uuid); - if (Strings.isNullOrEmpty(username)) - throw new IllegalArgumentException("Username is null or empty: " + username); - - if (PlayerUUIDs.isOnlineMode()) return uuid; - - // OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - // if (!player.hasPlayedBefore()) throw new RuntimeException("Player with UUID " + uuid + " doesn't exist."); - - UUID realUUID = OFFLINE_TO_ONLINE.get(uuid); - boolean cached = realUUID != null; - if (realUUID == null) { - try { - realUUID = MojangAPI.requestUsernameToUUID(username); - if (realUUID == null) { - ProfileLogger.debug("Caching null for {} ({}) because it doesn't exist.", username, uuid); - realUUID = IDENTITY_UUID; // Player not found, we should cache this information. - } else ONLINE_TO_OFFLINE.put(realUUID, uuid); - OFFLINE_TO_ONLINE.put(uuid, realUUID); - USERNAME_TO_ONLINE.put(username, realUUID); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - if (realUUID == IDENTITY_UUID) { - ProfileLogger.debug("Providing null UUID for {} ({}) because it doesn't exist.", username, uuid); - realUUID = null; - } else { - ProfileLogger.debug((cached ? "Cached " : "") + "Real UUID for {} ({}) is {}", username, uuid, realUUID); - } - - UUID offlineUUID = getOfflineUUID(username); - if (!uuid.equals(offlineUUID) && !uuid.equals(realUUID)) { - throw new RuntimeException("The provided UUID (" + uuid + ") for '" + username + - "' doesn't match the offline UUID (" + offlineUUID + ") or the real UUID (" + realUUID + ')'); - } - return realUUID; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfileLogger.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfileLogger.java deleted file mode 100644 index 1b1d66a93..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfileLogger.java +++ /dev/null @@ -1,14 +0,0 @@ -package taboolib.library.xseries.profiles; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public final class ProfileLogger { - public static final Logger LOGGER = LogManager.getLogger("XSkull"); - - public static void debug(String mainMessage, Object... variables) { - LOGGER.debug(mainMessage, variables); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfilesCore.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfilesCore.java deleted file mode 100644 index 4b209d191..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/ProfilesCore.java +++ /dev/null @@ -1,194 +0,0 @@ -package taboolib.library.xseries.profiles; - -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.FieldMemberHandle; -import taboolib.library.xseries.reflection.jvm.MethodMemberHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftClassHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import com.google.common.cache.LoadingCache; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.minecraft.MinecraftSessionService; -import com.mojang.authlib.properties.Property; -import org.jetbrains.annotations.ApiStatus; - -import java.lang.invoke.MethodHandle; -import java.net.Proxy; -import java.util.Map; -import java.util.UUID; - -import static taboolib.library.xseries.reflection.XReflection.v; - -/** - * Collection of NMS reflection needed to interact with the internal cache. - */ -@SuppressWarnings("unchecked") -@ApiStatus.Internal -public final class ProfilesCore { - public static final Object USER_CACHE, MINECRAFT_SESSION_SERVICE; - public static final Proxy PROXY; - public static final LoadingCache YggdrasilMinecraftSessionService_insecureProfiles; - - public static final Map UserCache_profilesByName; - public static final Map UserCache_profilesByUUID; - - public static final MethodHandle - FILL_PROFILE_PROPERTIES, GET_PROFILE_BY_NAME, GET_PROFILE_BY_UUID, CACHE_PROFILE, - CRAFT_META_SKULL_PROFILE_GETTER, CRAFT_META_SKULL_PROFILE_SETTER, - CRAFT_SKULL_PROFILE_SETTER, CRAFT_SKULL_PROFILE_GETTER, - Property_getValue, UserCache_getNextOperation, UserCacheEntry_getProfile, UserCacheEntry_setLastAccess; - - /** - * In v1.20.2, Mojang switched to {@code record} class types for their {@link Property} class. - */ - public static final boolean NULLABILITY_RECORD_UPDATE = XReflection.supports(1, 20, 2); - - static { - Object userCache, minecraftSessionService, insecureProfiles = null; - Proxy proxy; - MethodHandle fillProfileProperties = null, getProfileByName, getProfileByUUID, cacheProfile; - MethodHandle profileSetterMeta, profileGetterMeta; - - ReflectiveNamespace ns = XReflection.namespaced() - .imports(GameProfile.class, MinecraftSessionService.class, LoadingCache.class); - - MinecraftClassHandle GameProfileCache = ns.ofMinecraft( - "package nms.server.players; public class GameProfileCache {}" - ).map(MinecraftMapping.SPIGOT, "UserCache"); - - try { - MinecraftClassHandle CraftMetaSkull = ns.ofMinecraft( - "package cb.inventory; class CraftMetaSkull extends CraftMetaItem implements SkullMeta {}" - ); - profileGetterMeta = CraftMetaSkull.field("private GameProfile profile;").getter().reflect(); - - try { - // https://github.com/CryptoMorin/XSeries/issues/169 - // noinspection MethodMayBeStatic - profileSetterMeta = CraftMetaSkull.method("private void setProfile(GameProfile profile);").reflect(); - } catch (NoSuchMethodException e) { - profileSetterMeta = CraftMetaSkull.field("private GameProfile profile;").setter().reflect(); - } - - MinecraftClassHandle MinecraftServer = ns.ofMinecraft( - "package nms.server; public abstract class MinecraftServer {}" - ); - - // Added by Bukkit - Object minecraftServer = MinecraftServer.method("public static MinecraftServer getServer();").reflect().invoke(); - - minecraftSessionService = MinecraftServer.method("public MinecraftSessionService getSessionService();") - .named(/* 1.19.4 */ "ay", /* 1.17.1 */ "getMinecraftSessionService", "az", "ao", "am", /* 1.20.4 */ "aD", /* 1.20.6 */ "ar") - .reflect().invoke(minecraftServer); - - { - FieldMemberHandle insecureProfilesFieldHandle = ns.ofMinecraft("package com.mojang.authlib.yggdrasil;" + - "public class YggdrasilMinecraftSessionService implements MinecraftSessionService {}").field().getter(); - if (NULLABILITY_RECORD_UPDATE) { - insecureProfilesFieldHandle.signature("private final LoadingCache> insecureProfiles;"); - } else { - insecureProfilesFieldHandle.signature("private final LoadingCache insecureProfiles;"); - } - MethodHandle insecureProfilesField = insecureProfilesFieldHandle.reflectOrNull(); - if (insecureProfilesField != null) { - insecureProfiles = insecureProfilesField.invoke(minecraftSessionService); - } - } - - userCache = MinecraftServer.method("public GameProfileCache getProfileCache();") - .named("ar", /* 1.18.2 */ "ao", /* 1.20.4 */ "ap", /* 1.20.6 */ "au") - .map(MinecraftMapping.OBFUSCATED, /* 1.9.4 */ "getUserCache") - .reflect().invoke(minecraftServer); - - if (!NULLABILITY_RECORD_UPDATE) { - fillProfileProperties = ns.of(MinecraftSessionService.class).method( - "public GameProfile fillProfileProperties(GameProfile profile, boolean flag);" - ).reflect(); - } - - // noinspection MethodMayBeStatic - UserCache_getNextOperation = GameProfileCache.method("private long getNextOperation();") - .map(MinecraftMapping.OBFUSCATED, v(21, "e").v(16, "d").orElse("d")).reflectOrNull(); - - MethodMemberHandle profileByName = GameProfileCache.method().named(/* v1.17.1 */ "getProfile", "a"); - MethodMemberHandle profileByUUID = GameProfileCache.method().named(/* v1.17.1 */ "getProfile", "a"); - getProfileByName = XReflection.anyOf( - () -> profileByName.signature("public GameProfile get(String username);"), - () -> profileByName.signature("public Optional get(String username);") - ).reflect(); - getProfileByUUID = XReflection.anyOf( - () -> profileByUUID.signature("public GameProfile get(UUID id);"), - () -> profileByUUID.signature("public Optional get(UUID id);") - ).reflect(); - - cacheProfile = GameProfileCache.method("public void add(GameProfile profile);") - .map(MinecraftMapping.OBFUSCATED, "a").reflect(); - - try { - // Some versions don't have the public getProxy() method. It's very very inconsistent... - proxy = (Proxy) MinecraftServer.field("protected final java.net.Proxy proxy;").getter() - .map(MinecraftMapping.OBFUSCATED, v(20, 5, "h").v(20, 3, "i") - .v(19, "j") - .v(18, 2, "n").v(18, "o") - .v(17, "m") - .v(14, "proxy") // v1.14 -> v1.16 - .v(13, "c").orElse(/* v1.8 and v1.9 */ "e")) - .reflect().invoke(minecraftServer); - } catch (Throwable ex) { - ProfileLogger.LOGGER.error("Failed to initialize server proxy settings", ex); - proxy = null; - } - } catch (Throwable throwable) { - throw XReflection.throwCheckedException(throwable); - } - - MinecraftClassHandle CraftSkull = ns.ofMinecraft( - "package cb.block; public class CraftSkull extends CraftBlockEntityState implements Skull {}" - ); - - FieldMemberHandle craftProfile = CraftSkull.field("private GameProfile profile;"); - - Property_getValue = NULLABILITY_RECORD_UPDATE ? null : - ns.of(Property.class).method("public String getValue();").unreflect(); - - PROXY = proxy; - USER_CACHE = userCache; - YggdrasilMinecraftSessionService_insecureProfiles = (LoadingCache) insecureProfiles; - MINECRAFT_SESSION_SERVICE = minecraftSessionService; - FILL_PROFILE_PROPERTIES = fillProfileProperties; - GET_PROFILE_BY_NAME = getProfileByName; - GET_PROFILE_BY_UUID = getProfileByUUID; - CACHE_PROFILE = cacheProfile; - CRAFT_META_SKULL_PROFILE_SETTER = profileSetterMeta; - CRAFT_META_SKULL_PROFILE_GETTER = profileGetterMeta; - CRAFT_SKULL_PROFILE_SETTER = craftProfile.setter().unreflect(); - CRAFT_SKULL_PROFILE_GETTER = craftProfile.getter().unreflect(); - - MinecraftClassHandle UserCacheEntry = GameProfileCache - .inner("private static class GameProfileInfo {}") - .map(MinecraftMapping.SPIGOT, "UserCacheEntry"); - - UserCacheEntry_getProfile = UserCacheEntry.method("public GameProfile getProfile();") - .map(MinecraftMapping.OBFUSCATED, "a").makeAccessible() - .unreflect(); - UserCacheEntry_setLastAccess = UserCacheEntry.method("public void setLastAccess(long i);") - .map(MinecraftMapping.OBFUSCATED, "a").reflectOrNull(); - - try { - // private final Map profilesByName = Maps.newConcurrentMap(); - UserCache_profilesByName = (Map) GameProfileCache.field("private final Map profilesByName;") - .getter().map(MinecraftMapping.OBFUSCATED, v(17, "e").v(16, 2, "c").v(9, "d").orElse("c")) - .reflect().invoke(userCache); - // private final Map profilesByUUID = Maps.newConcurrentMap(); - UserCache_profilesByUUID = (Map) GameProfileCache.field("private final Map profilesByUUID;") - .getter().map(MinecraftMapping.OBFUSCATED, v(17, "f").v(16, 2, "d").v(9, "e").orElse("d")) - .reflect().invoke(userCache); - - // private final Deque f = new LinkedBlockingDeque(); Removed in v1.16 - // MethodHandle deque = GameProfileCache.field("private final Deque f;") - // .getter().reflectOrNull(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileFallback.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileFallback.java deleted file mode 100644 index f54b8c543..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileFallback.java +++ /dev/null @@ -1,36 +0,0 @@ -package taboolib.library.xseries.profiles.builder; - -import taboolib.library.xseries.profiles.exceptions.ProfileChangeException; -import taboolib.library.xseries.profiles.objects.ProfileContainer; - -/** - * An object that has all the information about failed attempts. - * @param the object that has its profile set. See {@link ProfileContainer} for a list. - */ -public final class ProfileFallback { - private final ProfileInstruction instruction; - private T object; - private final ProfileChangeException error; - - public ProfileFallback(ProfileInstruction instruction, T object, ProfileChangeException error) { - this.instruction = instruction; - this.object = object; - this.error = error; - } - - public T getObject() { - return object; - } - - public ProfileInstruction getInstruction() { - return instruction; - } - - public void setObject(T object) { - this.object = object; - } - - public ProfileChangeException getError() { - return error; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileInstruction.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileInstruction.java deleted file mode 100644 index 3cec63d48..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/ProfileInstruction.java +++ /dev/null @@ -1,234 +0,0 @@ -package taboolib.library.xseries.profiles.builder; - -import taboolib.library.xseries.profiles.ProfileLogger; -import taboolib.library.xseries.profiles.exceptions.InvalidProfileException; -import taboolib.library.xseries.profiles.exceptions.ProfileChangeException; -import taboolib.library.xseries.profiles.exceptions.ProfileException; -import taboolib.library.xseries.profiles.mojang.PlayerProfileFetcherThread; -import taboolib.library.xseries.profiles.mojang.ProfileRequestConfiguration; -import taboolib.library.xseries.profiles.objects.ProfileContainer; -import taboolib.library.xseries.profiles.objects.Profileable; -import com.mojang.authlib.GameProfile; -import org.bukkit.block.BlockState; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * Represents an instruction that sets a property of a {@link GameProfile}. - * It uses a {@link #profileContainer} to define how to set the property and - * a {@link #profileable} to define what to set in the property. - * - * @param The type of the result produced by the {@link #profileContainer} function. - */ -public final class ProfileInstruction implements Profileable { - /** - * The function called that applies the given {@link #profileable} to an object that supports it - * such as {@link ItemStack}, {@link SkullMeta} or a {@link BlockState}. - */ - private final ProfileContainer profileContainer; - /** - * The main profile to set. - */ - private Profileable profileable; - /** - * All fallback profiles to try if the main one fails. - */ - private final List fallbacks = new ArrayList<>(); - private Consumer> onFallback; - private ProfileRequestConfiguration profileRequestConfiguration; - - private boolean lenient = false; - - protected ProfileInstruction(ProfileContainer profileContainer) { - this.profileContainer = profileContainer; - } - - /** - * Removes the profile and skin texture from the item/block. - */ - public T removeProfile() { - profileContainer.setProfile(null); - return profileContainer.getObject(); - } - - @ApiStatus.Experimental - public ProfileInstruction profileRequestConfiguration(ProfileRequestConfiguration config) { - this.profileRequestConfiguration = config; - return this; - } - - /** - * Fails silently if any of the {@link ProfileException} errors occur. - * Mainly affects {@link Profileable#detect(String)} - */ - public ProfileInstruction lenient() { - this.lenient = true; - return this; - } - - /** - * The current profile of the item/block (not the profile provided in {@link #profile(Profileable)}) - */ - @Override - @Nullable - public GameProfile getProfile() { - return profileContainer.getProfile(); - } - - /** - * A string representation of the {@link #getProfile()} which is useful for data storage. - */ - @Nullable - public String getProfileString() { - return profileContainer.getProfileValue(); - } - - /** - * Sets the texture profile to be set to the item/block. Use one of the - * static methods of {@link Profileable} class. - */ - public ProfileInstruction profile(Profileable profileable) { - this.profileable = profileable; - return this; - } - - /** - * A list of fallback profiles in order. If the profile set in {@link #profile(Profileable)} fails, - * these profiles will be tested in order until a correct one is found, - * also if any of the fallback profiles are used, {@link #onFallback} will be called too. - * @see #apply() - */ - public ProfileInstruction fallback(Profileable... fallbacks) { - this.fallbacks.addAll(Arrays.asList(fallbacks)); - return this; - } - - /** - * Called when any of the {@link #fallback(Profileable...)} profiles are used, - * this is also called if no fallback profile is provided, but the main one {@link #profile(Profileable)} fails. - * @see #onFallback(Runnable) - */ - public ProfileInstruction onFallback(Consumer> onFallback) { - this.onFallback = onFallback; - return this; - } - - /** - * @see #onFallback(Consumer) - */ - public ProfileInstruction onFallback(Runnable onFallback) { - this.onFallback = (fallback) -> onFallback.run(); - return this; - } - - /** - * Sets the profile generated by the instruction to the result type synchronously. - * This is recommended if your code is already not on the main thread, or if you know - * that the skull texture doesn't need additional requests. - * - *

What are these additional requests?

- * This only applies to offline mode (cracked) servers. Since these servers use - * a cracked version of the player UUIDs and not their real ones, the real UUID - * needs to be known by requesting it from Mojang servers and this request which - * requires internet connection, will delay things a lot. - * - * @return The result after setting the generated profile. - * @throws ProfileChangeException If any type of {@link ProfileException} occurs, they will be accumulated - * in form of suppressed exceptions ({@link Exception#getSuppressed()}) in this single exception - * starting from the main profile, followed by the fallback profiles. - */ - public T apply() { - Objects.requireNonNull(profileable, "No profile was set"); - ProfileChangeException exception = null; - - List tries = new ArrayList<>(2 + fallbacks.size()); - tries.add(profileable); - tries.addAll(fallbacks); - if (lenient) tries.add(XSkull.getDefaultProfile()); - - boolean success = false; - boolean tryingFallbacks = false; - for (Profileable profileable : tries) { - try { - GameProfile gameProfile = profileable.getDisposableProfile(); - if (gameProfile != null) { - profileContainer.setProfile(gameProfile); - success = true; - break; - } else { - if (exception == null) { - exception = new ProfileChangeException("Could not set the profile for " + profileContainer); - } - exception.addSuppressed(new InvalidProfileException("Profile doesn't have a value: " + profileable)); - tryingFallbacks = true; - } - } catch (ProfileException ex) { - if (exception == null) { - exception = new ProfileChangeException("Could not set the profile for " + profileContainer); - } - exception.addSuppressed(ex); - tryingFallbacks = true; - } - } - - if (exception != null) { - if (success || lenient) ProfileLogger.debug("apply() silenced exception {}", exception); - else throw exception; - } - - T object = profileContainer.getObject(); - if (tryingFallbacks && this.onFallback != null) { - ProfileFallback fallback = new ProfileFallback<>(this, object, exception); - this.onFallback.accept(fallback); - object = fallback.getObject(); - } - return object; - } - - /** - * Asynchronously applies the instruction to generate a {@link GameProfile} and returns a {@link CompletableFuture}. - * This method is designed for non-blocking execution, allowing tasks to be performed - * in the background without blocking the server's main thread. - * This method will always execute async, even if the results are cached. - *
- *

Reference Issues

- * Note that while these methods apply to the item/block instances, passing these instances - * to certain methods, for example {@link org.bukkit.inventory.Inventory#setItem(int, ItemStack)} - * will create a NMS copy of that instance and use that instead. Which means if for example - * you're going to be using an item for an inventory, you'd have to set the item again - * manually to the inventory once this method is done. - *
{@code
-     * Inventory inventory = ...;
-     * XSkull.createItem().profile(player).applyAsync()
-     *     .thenAcceptAsync(item -> inventory.setItem(slot, item));
-     * }
- * - * To make this cleaner, you could change the first line of the item's lore to something like "Loading..." - * and set it to the inventory right away so the player knows that the data is not fully loaded. - * Once this method is done, you could change the lore back and set the item back to the inventory. - * (The lore is preferred because it has less text limit compared to the title, it also gives the player - * all the textual information they need rather than the visual information if you're in a hurry) - *


- *

Usage example:

- *
{@code
-     *   XSkull.createItem().profile(player).applyAsync()
-     *      .thenAcceptAsync(result -> {
-     *          // Additional processing...
-     *      }, runnable -> Bukkit.getScheduler().runTask(plugin, runnable));
-     * }
- * - * @return A {@link CompletableFuture} that will complete asynchronously. - */ - public CompletableFuture applyAsync() { - return CompletableFuture.supplyAsync(this::apply, PlayerProfileFetcherThread.EXECUTOR); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/XSkull.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/XSkull.java deleted file mode 100644 index 8e1306ea4..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/builder/XSkull.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2024 Crypto Morin - * - * 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 taboolib.library.xseries.profiles.builder; - -import taboolib.library.xseries.XMaterial; -import taboolib.library.xseries.profiles.PlayerProfiles; -import taboolib.library.xseries.profiles.objects.ProfileContainer; -import taboolib.library.xseries.profiles.objects.ProfileInputType; -import taboolib.library.xseries.profiles.objects.Profileable; -import taboolib.library.xseries.reflection.XReflection; -import com.mojang.authlib.GameProfile; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; - -/** - * A cross-version way to apply skin texture from different sources to items and blocks. - *

- * Some websites to get custom heads: - *

- *
- *

Usage

- * The basic usage format of this API is as follows: - *
{@code
- * XSkull.createItem().profile(Profileable.of(player)).apply();
- * XSkull.of(item/block).profile(Profileable.of(configStringValue)).apply();
- * }
- *

- * Note: Make sure to read {@link ProfileInstruction#applyAsync()} if you're going to - * be requesting heads in the main thread. - *

- * This API replaces {@link SkullMeta} and {@link Skull} which doesn't properly handle skulls - * in edge cases just like any other system that relies on the default Mojang handlers. - * It also specifically supports offline servers too. - * - *

Mechanism

- *

- * The basic premise behind this API is that the final skull data is contained in a {@link GameProfile} - * either by ID, name or encoded textures URL property. - *

- * Different versions of Minecraft client handle this differently. In newer versions the client seem - * to prioritize the texture property over the set UUID and name, in older versions however using the - * same UUID for all GameProfiles caused all skulls (that use base64) to look the same. - * The client is responsible for caching skull textures. If the download were to fail (either because of - * connection issues or invalid values) the client will cache that skull UUID and the skull - * will remain as a steve head until the client is completely restarted. - * I don't know if this cache system works across other servers or is just specific to one server. - * - * @author Crypto Morin, Erick Alexander - * @version 11.2.0 - * @see XMaterial - * @see XReflection - */ -public final class XSkull { - /** - * Creates a {@link ProfileInstruction} for an {@link ItemStack}. - * This method initializes a new player head. - * - * @return A {@link ProfileInstruction} that sets the profile for the generated {@link ItemStack}. - */ - public static ProfileInstruction createItem() { - return of(XMaterial.PLAYER_HEAD.parseItem()); - } - - /** - * Creates a {@link ProfileInstruction} for an {@link ItemStack}. - * - * @param stack The {@link ItemStack} to set the profile for. - * @return A {@link ProfileInstruction} that sets the profile for the given {@link ItemStack}. - */ - public static ProfileInstruction of(ItemStack stack) { - return new ProfileInstruction<>(new ProfileContainer.ItemStackProfileContainer(stack)); - } - - /** - * Creates a {@link ProfileInstruction} for an {@link ItemMeta}. - * - * @param meta The {@link ItemMeta} to set the profile for. - * @return An {@link ProfileInstruction} that sets the profile for the given {@link ItemMeta}. - */ - public static ProfileInstruction of(ItemMeta meta) { - return new ProfileInstruction<>(new ProfileContainer.ItemMetaProfileContainer((SkullMeta) meta)); - } - - /** - * Creates a {@link ProfileInstruction} for a {@link Block}. - * - * @param block The {@link Block} to set the profile for. - * @return An {@link ProfileInstruction} that sets the profile for the given {@link Block}. - */ - public static ProfileInstruction of(Block block) { - return new ProfileInstruction<>(new ProfileContainer.BlockProfileContainer(block)); - } - - /** - * Creates a {@link ProfileInstruction} for a {@link BlockState}. - * - * @param state The {@link BlockState} to set the profile for. - * @return An {@link ProfileInstruction} that sets the profile for the given {@link BlockState}. - */ - public static ProfileInstruction of(BlockState state) { - return new ProfileInstruction<>(new ProfileContainer.BlockStateProfileContainer((Skull) state)); - } - - - /** - * We'll just return an x shaped hardcoded skull.
- * minecraft-heads.com - */ - private static final GameProfile DEFAULT_PROFILE = PlayerProfiles.signXSeries(ProfileInputType.BASE64.getProfile( - "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5l" + - "Y3JhZnQubmV0L3RleHR1cmUvYzEwNTkxZTY5MDllNmEyODFiMzcxODM2ZTQ2MmQ2" + - "N2EyYzc4ZmEwOTUyZTkxMGYzMmI0MWEyNmM0OGMxNzU3YyJ9fX0=" - )); - - /** - * Retrieves the default {@link GameProfile} used by XSkull. - * This method creates a clone of the default profile to prevent modifications to the original. - * - * @return A clone of the default {@link GameProfile}. - */ - protected static Profileable getDefaultProfile() { - // We copy this just in case something changes the GameProfile properties. - GameProfile clone = PlayerProfiles.createGameProfile(DEFAULT_PROFILE.getId(), DEFAULT_PROFILE.getName()); - clone.getProperties().putAll(DEFAULT_PROFILE.getProperties()); - return Profileable.of(clone); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileContainerException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileContainerException.java deleted file mode 100644 index 2d6a3d892..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileContainerException.java +++ /dev/null @@ -1,14 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -/** - * When a provided item/block cannot contain skull textures. - */ -public final class InvalidProfileContainerException extends ProfileException { - public InvalidProfileContainerException(String message) { - super(message); - } - - public InvalidProfileContainerException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileException.java deleted file mode 100644 index f877be02f..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/InvalidProfileException.java +++ /dev/null @@ -1,16 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -import taboolib.library.xseries.profiles.objects.Profileable; - -/** - * if a given {@link Profileable} has incorrect value (more general than {@link UnknownPlayerException}) - */ -public class InvalidProfileException extends ProfileException { - public InvalidProfileException(String message) { - super(message); - } - - public InvalidProfileException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIException.java deleted file mode 100644 index e997b6e7e..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIException.java +++ /dev/null @@ -1,14 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -/** - * If any unknown non-recoverable network issues occur, unless it's a {@link MojangAPIRetryException} - */ -public class MojangAPIException extends ProfileException { - public MojangAPIException(String message) { - super(message); - } - - public MojangAPIException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIRetryException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIRetryException.java deleted file mode 100644 index 86eabe028..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/MojangAPIRetryException.java +++ /dev/null @@ -1,26 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -/** - * Due to being ratelimited or network issues that can be fixed if the request is sent later again. - */ -public final class MojangAPIRetryException extends MojangAPIException { - public enum Reason { - CONNECTION_RESET, CONNECTION_TIMEOUT, RATELIMITED - } - - private final Reason reason; - - public MojangAPIRetryException(Reason reason, String message) { - super(message); - this.reason = reason; - } - - public MojangAPIRetryException(Reason reason, String message, Throwable cause) { - super(message, cause); - this.reason = reason; - } - - public Reason getReason() { - return reason; - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileChangeException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileChangeException.java deleted file mode 100644 index 5fe8b99fb..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileChangeException.java +++ /dev/null @@ -1,16 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -import taboolib.library.xseries.profiles.builder.ProfileInstruction; - -/** - * Aggregate error container for {@link ProfileInstruction#apply()}. - */ -public final class ProfileChangeException extends ProfileException { - public ProfileChangeException(String message) { - super(message); - } - - public ProfileChangeException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileException.java deleted file mode 100644 index 7e18ef085..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/ProfileException.java +++ /dev/null @@ -1,25 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -/** - * Represents any known errors that can occur for the profiles API. - */ -public class ProfileException extends RuntimeException { - public ProfileException() { - } - - public ProfileException(String message) { - super(message); - } - - public ProfileException(String message, Throwable cause) { - super(message, cause); - } - - public ProfileException(Throwable cause) { - super(cause); - } - - public ProfileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/UnknownPlayerException.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/UnknownPlayerException.java deleted file mode 100644 index 574daa91c..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/exceptions/UnknownPlayerException.java +++ /dev/null @@ -1,12 +0,0 @@ -package taboolib.library.xseries.profiles.exceptions; - -import taboolib.library.xseries.profiles.objects.Profileable; - -/** - * If a specific player-identifying {@link Profileable} is not found. - */ -public final class UnknownPlayerException extends InvalidProfileException { - public UnknownPlayerException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MinecraftClient.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MinecraftClient.java deleted file mode 100644 index c062cc252..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MinecraftClient.java +++ /dev/null @@ -1,271 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import taboolib.library.xseries.profiles.ProfileLogger; -import taboolib.library.xseries.profiles.ProfilesCore; -import taboolib.library.xseries.profiles.exceptions.MojangAPIException; -import taboolib.library.xseries.profiles.exceptions.MojangAPIRetryException; -import taboolib.library.xseries.reflection.XReflection; -import com.google.common.base.Charsets; -import com.google.common.io.CharStreams; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.internal.Streams; -import com.google.gson.stream.JsonReader; -import org.bukkit.Bukkit; -import org.intellij.lang.annotations.Pattern; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Locale; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; - -/** - * Sends HTTP requests to Mojang API endpoints. - * @see MojangAPI - * @see MojangAPIException - * @see MojangAPIRetryException - */ -@ApiStatus.Internal -public class MinecraftClient { - private static final AtomicInteger SESSION_ID = new AtomicInteger(); - private static final Proxy PROXY = ProfilesCore.PROXY == null ? Proxy.NO_PROXY : ProfilesCore.PROXY; - private static final Gson GSON = new Gson(); - private static final RateLimiter TOTAL_REQUESTS = new RateLimiter(Integer.MAX_VALUE, Duration.ofMinutes(10)); - /** - * For example: - * XSeries/11.2.0 (X11; Linux x86_64; Oracle Corporation; 21.0.0) Paper/1.21-R0.1-SNAPSHOT 1.21-9-4ea696f (MC: 1.21) - */ - private static final String USER_AGENT = "XSeries/" + XReflection.XSERIES_VERSION + - " (" + System.getProperty("os.name") + "; " + System.getProperty("os.version") + "; " + - System.getProperty("java.vendor") + "; " + System.getProperty("java.version") + ") " + - Bukkit.getName() + '/' + Bukkit.getBukkitVersion() + ' ' + Bukkit.getVersion(); - private final String method; - private final URI baseURL; - private final RateLimiter rateLimiter; - - @SuppressWarnings("ReturnOfInnerClass") - public Session session(@Nullable ProfileRequestConfiguration config) { - Session session = new Session(); - if (config != null) config.configure(session); - return session; - } - - public MinecraftClient(@Pattern("GET|POST|PUT|DELETE") String method, String baseURL, RateLimiter rateLimiter) { - this.method = method; - try { - this.baseURL = new URI(baseURL); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - this.rateLimiter = rateLimiter; - } - - private static String totalReq() { - return " (total: " + TOTAL_REQUESTS.getEffectiveRequestsCount() + ')'; - } - - @SuppressWarnings("ReturnOfInnerClass") - public final class Session { - private final int sessionId = SESSION_ID.getAndIncrement(); - private Duration - connectTimeout = Duration.ofSeconds(10), - readTimeout = Duration.ofSeconds(10), - retryDelay = Duration.ofSeconds(5); - private int retries; - private boolean waitInQueue = true; - private Object body; - private String append; - private HttpURLConnection connection; - private BiFunction errorHandler; - - private void debug(String message, Object... vars) { - Object[] variables = XReflection.concatenate(new Object[]{sessionId, append}, vars); - ProfileLogger.debug("[MinecraftClient-{}][{}] " + message, variables); - } - - public Session exceptionally(BiFunction errorHandler) { - this.errorHandler = errorHandler; - return this; - } - - public Session waitInQueue(boolean wait) { - this.waitInQueue = wait; - return this; - } - - public Session retry(int retries, Duration delay) { - this.retries = retries; - this.retryDelay = delay; - return this; - } - - public Session body(Object body) { - validateMethod("POST"); - this.body = Objects.requireNonNull(body); - return this; - } - - public Session append(@Nonnull String append) { - this.append = Objects.requireNonNull(append); - return this; - } - - public Session timeout(Duration connectTimeout, Duration readTimeout) { - this.connectTimeout = connectTimeout; - this.readTimeout = readTimeout; - return this; - } - - private void validateMethod(String method) { - if (!MinecraftClient.this.method.equals(method)) - throw new UnsupportedOperationException( - "Cannot " + method + " with a client using method " + MinecraftClient.this); - } - - @Nullable - public JsonElement request() throws IOException, MojangAPIException { - try { - JsonElement response = request0(); - debug("Received response: {}", response); - return response == null ? null : (response.isJsonNull() ? null : response); - } catch (Exception ex) { - if (retries > 0) { - retries--; - if (!(ex instanceof MojangAPIRetryException) || - ((MojangAPIRetryException) ex).getReason() != MojangAPIRetryException.Reason.RATELIMITED) { - try { - Thread.sleep(retryDelay.toMillis()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - return request(); - } - if (errorHandler == null) throw ex; - else { - Boolean shouldRetry = errorHandler.apply(this, ex); - if (shouldRetry == null || shouldRetry) return request(); - else throw ex; - } - } - } - - @Nullable - private JsonElement request0() throws IOException, MojangAPIException { - if (waitInQueue) { - rateLimiter.acquireOrWait(); - } else { - if (!rateLimiter.acquire()) - throw new MojangAPIRetryException(MojangAPIRetryException.Reason.RATELIMITED, - "Rate limit has been hit! " + rateLimiter + totalReq()); - } - - connection = (HttpURLConnection) - (append == null ? baseURL : baseURL.resolve(append)).toURL().openConnection(PROXY); - connection.setRequestMethod(method); - connection.setConnectTimeout((int) connectTimeout.toMillis()); - connection.setReadTimeout((int) readTimeout.toMillis()); - connection.setDoInput(true); - connection.setUseCaches(false); - connection.setAllowUserInteraction(false); - - // Not used by the default authlib's client, but we're going to - // add it anyway just for the sake of networking and Mojang's server stats (if any?) - connection.setRequestProperty("User-Agent", USER_AGENT); - - // The token is only used for modifying operations like uploading a new skin. - // if (this.accessToken != null) { - // connection.setRequestProperty("Authorization", "Bearer " + this.accessToken); - // } - - if (body != null) { - connection.setDoOutput(true); - String stringBody = GSON.toJson(body); - debug("Writing body {} to {}", stringBody, connection.getURL()); - byte[] bodyBytes = stringBody.getBytes(StandardCharsets.UTF_8); - connection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); - connection.setRequestProperty("Content-Length", String.valueOf(bodyBytes.length)); - - try (OutputStream outputStream = connection.getOutputStream()) { - outputStream.write(bodyBytes); - } - } else { - connection.setDoOutput(false); - } - - debug("Sending request to {}", connection.getURL()); - - try { - return connectionStreamToJson(false); - } catch (Throwable ex) { - MojangAPIException exception; - try { - switch (connection.getResponseCode()) { - case HttpURLConnection.HTTP_NOT_FOUND: - return null; - case 429: // Too many requests - String rateLimitBefore = rateLimiter.toString(); - rateLimiter.instantRateLimit(); - throw new MojangAPIRetryException(MojangAPIRetryException.Reason.RATELIMITED, - "Rate limit has been hit (server confirmed): " + rateLimitBefore + " -> " + rateLimitBefore + totalReq()); - } - if (ex instanceof SocketException && ex.getMessage().toLowerCase(Locale.ENGLISH).contains("connection reset")) { - throw new MojangAPIRetryException(MojangAPIRetryException.Reason.CONNECTION_RESET, "Connection was closed", ex); - } - JsonElement errorJson = connectionStreamToJson(true); - exception = new MojangAPIException(errorJson == null ? "[NO ERROR RESPONSE]" : errorJson.toString(), ex); - } catch (MojangAPIRetryException rethrowEx) { - throw rethrowEx; - } catch (Throwable errorEx) { - exception = new MojangAPIException("Failed to read both normal response " + - "and error response from '" + connection.getURL() + '\''); - exception.addSuppressed(ex); - exception.addSuppressed(errorEx); - } - - throw exception; - } - } - - private JsonElement connectionStreamToJson(boolean error) throws IOException, RuntimeException { - try ( - InputStream inputStream = error ? connection.getErrorStream() : connection.getInputStream(); - ) { - if (error && inputStream == null) return null; - try (JsonReader reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - Exception ex = null; - JsonElement json; - try { - json = Streams.parse(reader); - } catch (Exception e) { - ex = e; - json = null; - } - if (json == null) { - // For UUID_TO_PROFILE, this happens when HTTP Code 204 (No Content) is given. - // And that happens if the UUID doesn't exist in Mojang servers. (E.g. cracked UUIDs) - String rawResponse = CharStreams.toString(new InputStreamReader( - error ? connection.getErrorStream() : connection.getInputStream(), - Charsets.UTF_8) - ); - throw new RuntimeException((error ? "error response" : "normal response") - + " is not a JSON object '" - + connection.getResponseCode() + " - " + connection.getResponseMessage() + "': " + - rawResponse, ex); - } - return json; - } - } - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangAPI.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangAPI.java deleted file mode 100644 index 2f84f8a94..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangAPI.java +++ /dev/null @@ -1,355 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import taboolib.library.xseries.profiles.PlayerUUIDs; -import taboolib.library.xseries.profiles.ProfileLogger; -import taboolib.library.xseries.profiles.ProfilesCore; -import taboolib.library.xseries.profiles.exceptions.MojangAPIException; -import taboolib.library.xseries.profiles.exceptions.UnknownPlayerException; -import taboolib.library.xseries.profiles.objects.ProfileInputType; -import taboolib.library.xseries.reflection.XReflection; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.Iterables; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.authlib.properties.PropertyMap; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.TimeUnit; - -@ApiStatus.Internal -public final class MojangAPI { - private static final MojangProfileCache MOJANG_PROFILE_CACHE = !ProfilesCore.NULLABILITY_RECORD_UPDATE ? - new MojangProfileCache.GameProfileCache(ProfilesCore.YggdrasilMinecraftSessionService_insecureProfiles) : - new MojangProfileCache.ProfileResultCache(ProfilesCore.YggdrasilMinecraftSessionService_insecureProfiles); - /** - * The 6hr expiration time is probably for players that update their skin, but 6hrs seems a bit too frequent. - */ - private static final Cache> INSECURE_PROFILES = CacheBuilder.newBuilder() - .expireAfterWrite(6L, TimeUnit.HOURS).build(); - - /** - * "requireSecure" parameter basically means ignore the cache and also use "unsigned=false" parameter. - */ - private static final boolean REQUIRE_SECURE_PROFILES = false; - - /** - * https://wiki.vg/Mojang_API#Username_to_UUID - */ - private static final MinecraftClient USERNAME_TO_UUID = new MinecraftClient( - "GET", - "https://api.mojang.com/users/profiles/minecraft/", - new RateLimiter(600, Duration.ofMinutes(10)) - ); - - /** - * https://wiki.vg/Mojang_API#Usernames_to_UUIDs - */ - private static final MinecraftClient USERNAMES_TO_UUIDS = new MinecraftClient( - "POST", - "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname", - new RateLimiter(600, Duration.ofMinutes(10)) - ); - - /** - * https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape - */ - private static final MinecraftClient UUID_TO_PROFILE = new MinecraftClient( - "GET", - "https://sessionserver.mojang.com/session/minecraft/profile/", - new RateLimiter(200, Duration.ofMinutes(1)) - ); - - /** - * @return null if a player with that username is not found. - */ - @Nullable - public static UUID requestUsernameToUUID(@Nonnull String username) throws IOException { - JsonElement requestElement = USERNAME_TO_UUID.session(null).append(username).request(); - if (requestElement == null) return null; - - JsonObject userJson = requestElement.getAsJsonObject(); - JsonElement idElement = userJson.get("id"); - if (idElement == null) - throw new RuntimeException("No 'id' field for UUID request for '" + username + "': " + userJson); - - return PlayerUUIDs.UUIDFromDashlessString(idElement.getAsString()); - } - - /** - * This method is here just for study purposes. - * Retrieves a cached {@link GameProfile} by username from the user cache. - * If the profile is not found in the cache, creates a new profile with the provided name. - * - * @param username The username of the profile to retrieve from the cache. - * @return The cached {@link GameProfile} corresponding to the username, or a new profile if not found. - */ - private static GameProfile getCachedProfileByUsername(String username) { - try { - // Expires after every month calendar.add(2, 1); (Persists between restarts) - @Nullable Object profile = ProfilesCore.GET_PROFILE_BY_NAME.invoke(ProfilesCore.USER_CACHE, username); - if (profile instanceof Optional) profile = ((Optional) profile).orElse(null); - GameProfile gameProfile = profile == null ? - PlayerProfiles.createGameProfile(PlayerUUIDs.IDENTITY_UUID, username) : - PlayerProfiles.sanitizeProfile((GameProfile) profile); - ProfileLogger.debug("The cached profile for {} -> {}", username, profile); - return gameProfile; - } catch (Throwable throwable) { - ProfileLogger.LOGGER.error("Unable to get cached profile by username: {}", username, throwable); - return null; - } - } - - public static Optional getMojangCachedProfileFromUsername(String username) { - try { - return getMojangCachedProfileFromUsername0(username); - } catch (Throwable e) { - throw XReflection.throwCheckedException(e); - } - } - - private static Optional getMojangCachedProfileFromUsername0(String username) throws Throwable { - String normalized = username.toLowerCase(Locale.ROOT); - Object userCacheEntry = ProfilesCore.UserCache_profilesByName.get(normalized); - Optional optional; - - // We are supposed to be doing UserCacheEntry#getExpiration() check here - // but the cache already has a regular cleanup task (PlayerList#placeNewPlayer), - // we don't need that much accuracy. - - if (userCacheEntry != null) { - // The side effects of setLastAccess() is really insignificant, it's used for UserCache#getTopMRUProfiles() - // (MRU = Most Recently Used) which saves game profile cache to usercache.json from most accessed to least accessed. - // This is because of "settings.user-cache-size" spigot.yml option. - if (ProfilesCore.UserCacheEntry_setLastAccess != null && ProfilesCore.UserCache_getNextOperation != null) { - // usercache_usercacheentry.setLastAccess(this.getNextOperation()); - long nextOperation = (long) ProfilesCore.UserCache_getNextOperation.invoke(ProfilesCore.USER_CACHE); - ProfilesCore.UserCacheEntry_setLastAccess.invoke(userCacheEntry, nextOperation); - } - optional = Optional.of((GameProfile) ProfilesCore.UserCacheEntry_getProfile.invoke(userCacheEntry)); - } else { - // optional = lookupGameProfile(this.profileRepository, username); // CraftBukkit - use correct case for offline players - UUID realUUID = PlayerUUIDs.getRealUUIDOfPlayer(username); - if (realUUID == null) return Optional.empty(); - GameProfile profile = PlayerProfiles.createGameProfile( - PlayerUUIDs.isOnlineMode() ? realUUID : PlayerUUIDs.getOfflineUUID(username), - username - ); - optional = Optional.of(profile); - // this.add((GameProfile) optional.get()); - cacheProfile(profile); - } - - return optional; - } - - public static Map usernamesToUUIDs(@Nonnull Collection usernames, @Nullable ProfileRequestConfiguration config) { - if (usernames == null || usernames.isEmpty()) throw new IllegalArgumentException("Usernames are null or empty"); - for (String username : usernames) { - if (username == null || !ProfileInputType.USERNAME.pattern.matcher(username).matches()) { - throw new IllegalArgumentException("One of the requested usernames is invalid: " + username + " in " + usernames); - } - } - - Map mapped = new HashMap<>(usernames.size()); - Set finalUsernames = new HashSet<>(usernames); - { - // Remove duplicate & cached names - Iterator usernameIter = finalUsernames.iterator(); - while (usernameIter.hasNext()) { - String username = usernameIter.next(); - UUID cached = PlayerUUIDs.USERNAME_TO_ONLINE.get(username); - if (cached != null) { - usernameIter.remove(); - mapped.put(cached, username); - } - } - } - - if (finalUsernames.isEmpty()) return mapped; - boolean onlineMode = PlayerUUIDs.isOnlineMode(); - - // For some reason, the YggdrasilGameProfileRepository partitions names in pairs instead of 10s. - // It also "normalizes" names with lowercase and sends the request. - Iterable> partition = Iterables.partition(finalUsernames, 10); - for (List batch : partition) { - JsonArray response; - try { - // The wiki says that: - // BadRequestException is returned when any of the usernames is null or otherwise invalid - // But I'm not sure what that means in this context... but invalid usernames are just ignored, - // and no response is contained in the final result regarding them. - response = USERNAMES_TO_UUIDS.session(config).body(batch).request().getAsJsonArray(); - } catch (IOException ex) { - throw new MojangAPIException("Failed to request UUIDs for username batch: " + batch, ex); - } - - for (JsonElement element : response) { - JsonObject obj = element.getAsJsonObject(); - String name = obj.get("name").getAsString(); - UUID realId = PlayerUUIDs.UUIDFromDashlessString(obj.get("id").getAsString()); - UUID offlineId = PlayerUUIDs.getOfflineUUID(name); - - PlayerUUIDs.USERNAME_TO_ONLINE.put(name, realId); - PlayerUUIDs.ONLINE_TO_OFFLINE.put(realId, offlineId); - PlayerUUIDs.OFFLINE_TO_ONLINE.put(offlineId, realId); - if (!ProfilesCore.UserCache_profilesByName.containsKey(name)) { - cacheProfile(PlayerProfiles.createGameProfile(onlineMode ? realId : offlineId, name)); - } - - String prev = mapped.put(realId, name); - if (prev != null) - throw new RuntimeException("Got duplicate usernames for UUID: " + realId + " (" + prev + " -> " + name + ')'); - } - } - - return mapped; - } - - /** - * Retrieves a cached {@link GameProfile} by UUID from the user cache. - * If the profile is not found in the cache, creates a new profile with the provided UUID. - * - * @param uuid The UUID of the profile to retrieve from the cache. - * @return The cached {@link GameProfile} corresponding to the UUID, or a new profile if not found. - */ - @Nonnull - public static GameProfile getCachedProfileByUUID(UUID uuid) { - uuid = PlayerUUIDs.isOnlineMode() ? uuid : PlayerUUIDs.ONLINE_TO_OFFLINE.getOrDefault(uuid, uuid); - try { - @Nullable Object profile = ProfilesCore.GET_PROFILE_BY_UUID.invoke(ProfilesCore.USER_CACHE, uuid); - if (profile instanceof Optional) profile = ((Optional) profile).orElse(null); - ProfileLogger.debug("The cached profile for {} -> {}", uuid, profile); - return profile == null ? - PlayerProfiles.createNamelessGameProfile(uuid) : - PlayerProfiles.sanitizeProfile((GameProfile) profile); - } catch (Throwable throwable) { - ProfileLogger.LOGGER.error("Unable to get cached profile by UUID: {}", uuid, throwable); - return PlayerProfiles.createNamelessGameProfile(uuid); - } - } - - /** - * Caches the provided {@link GameProfile} in the user cache. - * These caches are also stored in {@code usercache.json} (file specified in net.minecraft.server.Services). - * - * @param profile The {@link GameProfile} to cache. - */ - private static void cacheProfile(GameProfile profile) { - try { - ProfilesCore.CACHE_PROFILE.invoke(ProfilesCore.USER_CACHE, profile); - ProfileLogger.debug("Profile is now cached: {}", profile); - } catch (Throwable throwable) { - ProfileLogger.LOGGER.error("Unable to cache profile {}", profile); - throwable.printStackTrace(); - } - } - - /** - * Fetches additional properties for the given {@link GameProfile} if possible (like the texture) and caches the result. - * - * @param profile The {@link GameProfile} for which properties are to be fetched. - * @return The updated {@link GameProfile} with fetched properties, sanitized for consistency. - * @throws UnknownPlayerException if a player with the specified profile properties (username and UUID) doesn't exist. - */ - @SuppressWarnings("OptionalAssignedToNull") - @Nonnull - public static GameProfile getOrFetchProfile(@Nonnull final GameProfile profile) throws UnknownPlayerException { - // Get real UUID for offline players - UUID realUUID; - if (profile.getName().equals(PlayerProfiles.DEFAULT_PROFILE_NAME)) { - // We will assume that the requested UUID is the real one - // since the server cache didn't find it and that player never - // joined this server. - // There is no way to tell if this is fake or real UUID, the - // closest we can get is to just request it from the server - // and see if it exists. (We can't reverse UUID.nameUUIDFromBytes) - realUUID = profile.getId(); - } else { - realUUID = PlayerUUIDs.getRealUUIDOfPlayer(profile.getName(), profile.getId()); - if (realUUID == null) { - throw new UnknownPlayerException("Player with the given properties not found: " + profile); - } - } - - Optional cached = INSECURE_PROFILES.getIfPresent(realUUID); - // noinspection OptionalAssignedToNull - if (cached != null) { - ProfileLogger.debug("Found cached profile from UUID ({}): {} -> {}", realUUID, profile, cached); - if (cached.isPresent()) return cached.get(); - else throw new UnknownPlayerException("Player with the given properties not found: " + profile); - } - - Optional mojangCache = MOJANG_PROFILE_CACHE.get(realUUID, profile); - if (mojangCache != null) { - INSECURE_PROFILES.put(realUUID, mojangCache); - if (mojangCache.isPresent()) return mojangCache.get(); - else throw new UnknownPlayerException("Player with the given properties not found: " + profile); - } - - JsonElement request; - try { - request = UUID_TO_PROFILE.session(null) - .append(PlayerUUIDs.toUndashedUUID(realUUID) + "?unsigned=" + !REQUIRE_SECURE_PROFILES) - .request(); - } catch (IOException e) { - throw new RuntimeException(e); - } - if (request == null) { - INSECURE_PROFILES.put(realUUID, Optional.empty()); - MOJANG_PROFILE_CACHE.cache(new PlayerProfile(realUUID, profile, null, null)); - throw new UnknownPlayerException("Player with the given properties not found: " + profile); - } - JsonObject profileData = request.getAsJsonObject(); - - UUID id = PlayerUUIDs.UUIDFromDashlessString(profileData.get("id").getAsString()); - String name = profileData.get("name").getAsString(); - GameProfile fetchedProfile = PlayerProfiles.createGameProfile(id, name); - - JsonElement propertiesEle = profileData.get("properties"); - if (propertiesEle != null) { - JsonArray props = propertiesEle.getAsJsonArray(); - PropertyMap properties = fetchedProfile.getProperties(); - for (JsonElement prop : props) { - JsonObject obj = prop.getAsJsonObject(); - String propName = obj.get("name").getAsString(); - String propValue = obj.get("value").getAsString(); - JsonElement sig = obj.get("signature"); - - Property property; - if (sig != null) { - property = new Property(propName, propValue, sig.getAsString()); - } else { - property = new Property(propName, propValue); - } - - properties.put(propName, property); - } - } - - List profileActions = new ArrayList<>(); - JsonElement profileActionsElement = profileData.get("profileActions"); - if (profileActionsElement != null) { - for (JsonElement action : profileActionsElement.getAsJsonArray()) { - profileActions.add(action.getAsString()); - } - } - - fetchedProfile = PlayerProfiles.sanitizeProfile(fetchedProfile); - cacheProfile(fetchedProfile); - - INSECURE_PROFILES.put(realUUID, Optional.of(fetchedProfile)); - MOJANG_PROFILE_CACHE.cache(new PlayerProfile(realUUID, profile, fetchedProfile, profileActions)); - - return fetchedProfile; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangProfileCache.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangProfileCache.java deleted file mode 100644 index b12615582..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/MojangProfileCache.java +++ /dev/null @@ -1,96 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import com.google.common.base.Strings; -import com.google.common.cache.LoadingCache; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.yggdrasil.ProfileActionType; -import com.mojang.authlib.yggdrasil.ProfileResult; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * A class to use and change the internal cache used for {@link GameProfile}. - */ -@ApiStatus.Internal -abstract class MojangProfileCache { - abstract void cache(PlayerProfile playerProfile); - - /** - * @return null if it's not in the cache. {@link Optional#empty()} if the player profile - * didn't exist and that result was cached. - */ - @Nullable - abstract Optional get(UUID realId, GameProfile gameProfile); - - protected static final class ProfileResultCache extends MojangProfileCache { - private final LoadingCache> insecureProfiles; - - @SuppressWarnings("unchecked") - ProfileResultCache(LoadingCache insecureProfiles) { - this.insecureProfiles = (LoadingCache>) insecureProfiles; - } - - @Override - void cache(PlayerProfile playerProfile) { - if (playerProfile.exists()) { - ProfileResult profileResult = new ProfileResult(playerProfile.fetchedGameProfile, - playerProfile.profileActions.stream().map(x -> { - try { - return ProfileActionType.valueOf(x); - } catch (IllegalArgumentException ex) { - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toSet())); - insecureProfiles.put(playerProfile.realUUID, Optional.of(profileResult)); - } else { - insecureProfiles.put(playerProfile.realUUID, Optional.empty()); - } - } - - @SuppressWarnings("OptionalAssignedToNull") - @Override - Optional get(UUID realId, GameProfile gameProfile) { - Optional cache = insecureProfiles.getIfPresent(realId); - return cache == null ? null : cache.map(ProfileResult::profile); - } - } - - @SuppressWarnings("OptionalAssignedToNull") - protected static final class GameProfileCache extends MojangProfileCache { - private final LoadingCache insecureProfiles; - - @SuppressWarnings("unchecked") - GameProfileCache(LoadingCache insecureProfiles) { - this.insecureProfiles = (LoadingCache) insecureProfiles; - } - - @Override - void cache(PlayerProfile playerProfile) { - if (playerProfile.exists()) { - insecureProfiles.put(playerProfile.requestedGameProfile, playerProfile.fetchedGameProfile); - } else { - insecureProfiles.put(playerProfile.requestedGameProfile, PlayerProfiles.NIL); - } - } - - @Override - @Nullable - Optional get(UUID realId, GameProfile gameProfile) { - // This is probably not going to work most of the time since the whole GameProfile - // object is used to hash the key, and the name isn't always provided to us. - String profileName = gameProfile.getName(); - if (Strings.isNullOrEmpty(profileName) || profileName.equals(PlayerProfiles.DEFAULT_PROFILE_NAME)) - return null; - - GameProfile cache = insecureProfiles.getIfPresent(new GameProfile(realId, gameProfile.getName())); - if (cache == PlayerProfiles.NIL) return Optional.empty(); - return cache == null ? null : Optional.of(cache); - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfile.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfile.java deleted file mode 100644 index dd89b34d9..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfile.java +++ /dev/null @@ -1,26 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import com.mojang.authlib.GameProfile; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; -import java.util.UUID; - -@ApiStatus.Internal -final class PlayerProfile { - public final UUID realUUID; - public final GameProfile requestedGameProfile, fetchedGameProfile; - public final List profileActions; - - PlayerProfile(UUID realUUID, GameProfile requestedGameProfile, - GameProfile fetchedGameProfile, List profileActions) { - this.realUUID = realUUID; - this.requestedGameProfile = requestedGameProfile; - this.fetchedGameProfile = fetchedGameProfile; - this.profileActions = profileActions; - } - - boolean exists() { - return fetchedGameProfile != null; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfileFetcherThread.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfileFetcherThread.java deleted file mode 100644 index b4a35a581..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/PlayerProfileFetcherThread.java +++ /dev/null @@ -1,29 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import taboolib.library.xseries.profiles.ProfileLogger; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -@ApiStatus.Internal -public final class PlayerProfileFetcherThread implements ThreadFactory { - /** - * An executor service with a fixed thread pool of size 2, used for asynchronous operations. - */ - public static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2, new PlayerProfileFetcherThread()); - - private static final AtomicInteger COUNT = new AtomicInteger(); - - @Override - public Thread newThread(@Nonnull final Runnable run) { - final Thread thread = new Thread(run); - thread.setName("Profile Lookup Executor #" + COUNT.getAndIncrement()); - thread.setUncaughtExceptionHandler((t, throwable) -> - ProfileLogger.LOGGER.error("Uncaught exception in thread {}", t.getName(), throwable)); - return thread; - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/ProfileRequestConfiguration.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/ProfileRequestConfiguration.java deleted file mode 100644 index e38ab348e..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/ProfileRequestConfiguration.java +++ /dev/null @@ -1,5 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -public interface ProfileRequestConfiguration { - void configure(MinecraftClient.Session session); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/RateLimiter.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/RateLimiter.java deleted file mode 100644 index 8174924b6..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/mojang/RateLimiter.java +++ /dev/null @@ -1,115 +0,0 @@ -package taboolib.library.xseries.profiles.mojang; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Unmodifiable; - -import java.time.Duration; -import java.util.Iterator; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Used for {@link MojangAPI} requests. - * The rate limits make things difficult specially in BungeeCord servers where - * a single dedicated server is used for multiple subservers. - * This will specially not work well at all if in shared hosting - * that can make requests almost impossible with all the servers - * in a single node. - * Another issue is that XSeries is mostly intended to be shaded by individual plugins - * so while we are directly helping the internal Mojang cache, there are some - * requests that Mojang doesn't have a cache for, so this creates another big - * problem of having to use separate caches that are not shared. - *

- * However, these are all untested speculation. But one can't imagine how else - * they'd identify a request other than the sender's IP because the default - * client used by Mojang doesn't even use a {@code User-Agent}. - * According to a comment by a Mojang web service admin in this JIRA issue - * shared VPS are indeed an issue due to shared IPs. - *

- * According to Mojang API the rate limit - * is around 600 requests per 10 (i.e. 1 request per second) for most endpoints. - * However UUID to Profile and Skin/Cape - * is around 200 requests per minute. - */ -@ApiStatus.Internal -public final class RateLimiter { - private final ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue<>(); - private final int maxRequests; - private final long per; - - RateLimiter(int maxRequests, Duration per) { - this.maxRequests = maxRequests; - this.per = per.toMillis(); - } - - @Unmodifiable - private ConcurrentLinkedQueue getRequests() { - if (requests.isEmpty()) return requests; - - // Implementing a cleanup delay is practically not any different - // from just letting this loop to happen in terms of performance. - long now = System.currentTimeMillis(); - Iterator iter = requests.iterator(); - while (iter.hasNext()) { - long requestedAt = iter.next(); - long diff = now - requestedAt; - if (diff > per) iter.remove(); - else break; // Requests are ordered, so if this fails, the others will fail too. - } - - return requests; - } - - public int getRemainingRequests() { - return Math.max(0, maxRequests - getRequests().size()); - } - - public int getEffectiveRequestsCount() { - return getRequests().size(); - } - - public void instantRateLimit() { - long now = System.currentTimeMillis(); - for (int i = 0; i < getRemainingRequests(); i++) { - requests.add(now); - } - } - - public boolean acquire() { - if (getRemainingRequests() <= 0) { - return false; - } else { - requests.add(System.currentTimeMillis()); - return true; - } - } - - public Duration timeUntilNextFreeRequest() { - if (getRemainingRequests() == 0) { - long now = System.currentTimeMillis(); - long oldestRequestedAt = requests.peek(); - long diff = now - oldestRequestedAt; - return Duration.ofMillis(per - diff); - } - return Duration.ZERO; - } - - public synchronized void acquireOrWait() { - long sleepUntil = timeUntilNextFreeRequest().toMillis(); - if (sleepUntil == 0) return; - try { - Thread.sleep(sleepUntil); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - @Override - public String toString() { - return getClass().getSimpleName() + - "[total=" + getRequests().size() + - ", remaining=" + getRemainingRequests() + - ", maxRequests=" + maxRequests + - ", per=" + per + - ']'; - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileContainer.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileContainer.java deleted file mode 100644 index ac872ad32..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileContainer.java +++ /dev/null @@ -1,150 +0,0 @@ -package taboolib.library.xseries.profiles.objects; - -import taboolib.library.xseries.profiles.ProfilesCore; -import taboolib.library.xseries.profiles.exceptions.InvalidProfileContainerException; -import com.mojang.authlib.GameProfile; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import javax.annotation.Nonnull; -import java.util.Objects; - -/** - * Represenets any object that has a {@link GameProfile} which can also be changed. - * @param the bukkit object. - */ -@ApiStatus.Internal -public abstract class ProfileContainer implements Profileable { - @Nonnull - public abstract void setProfile(@Nullable GameProfile profile); - - public abstract T getObject(); - - @Override - public final String toString() { - return this.getClass().getSimpleName() + '[' + getObject() + ']'; - } - - public static final class ItemStackProfileContainer extends ProfileContainer { - private final ItemStack itemStack; - - public ItemStackProfileContainer(ItemStack itemStack) {this.itemStack = Objects.requireNonNull(itemStack);} - - private ItemMetaProfileContainer getMetaContainer(ItemMeta meta) { - if (!(meta instanceof SkullMeta)) - throw new InvalidProfileContainerException("Item can't contain texture: " + itemStack); - return new ItemMetaProfileContainer((SkullMeta) meta); - } - - @Override - public void setProfile(GameProfile profile) { - ItemMeta meta = itemStack.getItemMeta(); - getMetaContainer(meta).setProfile(profile); - itemStack.setItemMeta(meta); - } - - @Override - public ItemStack getObject() { - return itemStack; - } - - @Override - public GameProfile getProfile() { - return getMetaContainer(itemStack.getItemMeta()).getProfile(); - } - } - - public static final class ItemMetaProfileContainer extends ProfileContainer { - private final ItemMeta meta; - - public ItemMetaProfileContainer(SkullMeta meta) {this.meta = Objects.requireNonNull(meta);} - - @Override - public void setProfile(GameProfile profile) { - try { - ProfilesCore.CRAFT_META_SKULL_PROFILE_SETTER.invoke(meta, profile); - } catch (Throwable throwable) { - throw new RuntimeException("Unable to set profile " + profile + " to " + meta, throwable); - } - } - - @Override - public ItemMeta getObject() { - return meta; - } - - @Override - public GameProfile getProfile() { - try { - return (GameProfile) ProfilesCore.CRAFT_META_SKULL_PROFILE_GETTER.invoke((SkullMeta) meta); - } catch (Throwable throwable) { - throw new RuntimeException("Failed to get profile from item meta: " + meta, throwable); - } - } - } - - public static final class BlockProfileContainer extends ProfileContainer { - private final Block block; - - public BlockProfileContainer(Block block) {this.block = Objects.requireNonNull(block);} - - private Skull getBlockState() { - BlockState state = block.getState(); - if (!(state instanceof Skull)) - throw new InvalidProfileContainerException("Block can't contain texture: " + block); - return (Skull) state; - } - - @Override - public void setProfile(GameProfile profile) { - Skull state = getBlockState(); - new BlockStateProfileContainer(state).setProfile(profile); - state.update(true); - } - - @Override - public Block getObject() { - return block; - } - - @Override - public GameProfile getProfile() { - return new BlockStateProfileContainer(getBlockState()).getProfile(); - } - } - - public static final class BlockStateProfileContainer extends ProfileContainer { - private final Skull state; - - public BlockStateProfileContainer(Skull state) {this.state = Objects.requireNonNull(state);} - - @Override - public void setProfile(GameProfile profile) { - try { - ProfilesCore.CRAFT_SKULL_PROFILE_SETTER.invoke(state, profile); - } catch (Throwable throwable) { - throw new RuntimeException("Unable to set profile " + profile + " to " + state, throwable); - } - } - - @Override - public Skull getObject() { - return state; - } - - @Override - public GameProfile getProfile() { - try { - return (GameProfile) ProfilesCore.CRAFT_SKULL_PROFILE_GETTER.invoke(state); - } catch (Throwable throwable) { - throw new RuntimeException("Unable to get profile fr om blockstate: " + state, throwable); - } - } - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileInputType.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileInputType.java deleted file mode 100644 index 83b6f2a41..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/ProfileInputType.java +++ /dev/null @@ -1,144 +0,0 @@ -package taboolib.library.xseries.profiles.objects; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import taboolib.library.xseries.profiles.exceptions.InvalidProfileException; -import com.mojang.authlib.GameProfile; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import javax.annotation.Nonnull; -import java.util.Arrays; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Different types of input patterns that can be used for identifying - * constructing, and validating various formats that can represent a {@link Profileable}. - */ -@SuppressWarnings("JavadocLinkAsPlainText") -public enum ProfileInputType { - /** - * Represents a texture hash pattern. - * Mojang hashes length are inconsistent, and they don't seem to use uppercase characters. - *

- * Currently, the shortest observed hash value is (lenght: 57): 0a4050e7aacc4539202658fdc339dd182d7e322f9fbcc4d5f99b5718a - *

- * Example: e5461a215b325fbdf892db67b7bfb60ad2bf1580dc968a15dfb304ccd5e74db - */ - TEXTURE_HASH(Pattern.compile("[0-9a-z]{55,70}")) { - @Override - public GameProfile getProfile(String textureHash) { - String base64 = PlayerProfiles.encodeBase64(PlayerProfiles.TEXTURES_NBT_PROPERTY_PREFIX + PlayerProfiles.TEXTURES_BASE_URL + textureHash + "\"}}}"); - return PlayerProfiles.profileFromHashAndBase64(textureHash, base64); - } - }, - - /** - * Represents a texture URL pattern that includes the base URL followed by the texture hash pattern. - *

- * Example: http://textures.minecraft.net/texture/e5461a215b325fbdf892db67b7bfb60ad2bf1580dc968a15dfb304ccd5e74db - */ - TEXTURE_URL(Pattern.compile("(?:https?://)?(?:textures\\.)?minecraft\\.net/texture/(?" + TEXTURE_HASH.pattern + ')', Pattern.CASE_INSENSITIVE)) { - @Override - public GameProfile getProfile(String textureUrl) { - String hash = extractTextureHash(textureUrl); - return TEXTURE_HASH.getProfile(hash); - } - }, - - /** - * Represents a Base64 encoded string pattern. - * The base64 pattern that's checked is not a general base64 pattern, but a pattern that - * closely represents the base64 genereated by the NBT data. - */ - BASE64(Pattern.compile("[-A-Za-z0-9+/]{100,}={0,3}")) { - @Override - public GameProfile getProfile(String base64) { - // The base64 string represents the textures. - // There are 3 types of textures: SKIN - CAPE - ELYTRA (not present in v1.8) - // Each can have a URL and an additional set of metadata (like model of skin, steve or alex, classic or slim) - // (from authlib's MinecraftProfileTexture which exists in all versions 1.8-1.21) - String decodedBase64 = PlayerProfiles.decodeBase64(base64); - if (decodedBase64 == null) - throw new InvalidProfileException("Not a base64 string: " + base64); - - String textureHash = extractTextureHash(decodedBase64); - if (textureHash == null) - throw new InvalidProfileException("Can't extract texture hash from base64: " + decodedBase64); - - return PlayerProfiles.profileFromHashAndBase64(textureHash, base64); - } - }, - - /** - * Represents a UUID pattern, following the standard UUID format. - */ - UUID(Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) { - // Case-insensitive flag doesn't work for some UUIDs. - @Override - public GameProfile getProfile(String uuidString) { - return Profileable.of(java.util.UUID.fromString(uuidString)).getProfile(); - } - }, - - /** - * Represents a username pattern, allowing alphanumeric characters and underscores, with a length of 1 to 16 characters. - * Minecraft now requires the username to be at least 3 characters long, but older accounts are still around. - * It also seems that there are a few inactive accounts that use spaces in their usernames? - */ - USERNAME(Pattern.compile("[A-Za-z0-9_]{1,16}")) { - @Override - public GameProfile getProfile(String username) { - return Profileable.username(username).getProfile(); - } - }; - - /** - * The RegEx pattern associated with the input type. - */ - @ApiStatus.Internal - public final Pattern pattern; - private static final ProfileInputType[] VALUES = values(); - - ProfileInputType(Pattern pattern) { - this.pattern = pattern; - } - - /** - * Retrieves a {@link GameProfile} based on the provided input string. - * - * @param input The input string to retrieve the profile for. - * @return The {@link GameProfile} corresponding to the input string. - */ - public abstract GameProfile getProfile(String input); - - /** - * Returns the corresponding {@link ProfileInputType} for the given identifier, if it matches any pattern. - * - * @param identifier The string to be checked against the patterns. - * @return The matching type, or {@code null} if no match is found. - */ - @Nullable - public static ProfileInputType typeOf(@Nonnull String identifier) { - Objects.requireNonNull(identifier, "Identifier cannot be null"); - return Arrays.stream(VALUES) - .filter(value -> value.pattern.matcher(identifier).matches()) - .findFirst().orElse(null); - } - - /** - * Extracts the texture hash from the provided input string. - *

- * Will not work reliably if NBT is passed: {"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/74133f6ac3be2e2499a784efadcfffeb9ace025c3646ada67f3414e5ef3394"}}} - * because there are several other texture types, see {@link #BASE64} inner comments. - * - * @param input The input string containing the texture hash. - * @return The extracted texture hash. - */ - @Nullable - private static String extractTextureHash(String input) { - Matcher matcher = ProfileInputType.TEXTURE_HASH.pattern.matcher(input); - return matcher.find() ? matcher.group() : null; - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/Profileable.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/Profileable.java deleted file mode 100644 index ed734ed26..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/Profileable.java +++ /dev/null @@ -1,339 +0,0 @@ -package taboolib.library.xseries.profiles.objects; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import taboolib.library.xseries.profiles.PlayerUUIDs; -import taboolib.library.xseries.profiles.exceptions.InvalidProfileException; -import taboolib.library.xseries.profiles.exceptions.UnknownPlayerException; -import taboolib.library.xseries.profiles.mojang.MojangAPI; -import taboolib.library.xseries.profiles.mojang.PlayerProfileFetcherThread; -import taboolib.library.xseries.profiles.mojang.ProfileRequestConfiguration; -import taboolib.library.xseries.profiles.objects.cache.TimedCacheableProfileable; -import taboolib.library.xseries.profiles.objects.transformer.ProfileTransformer; -import taboolib.library.xseries.profiles.objects.transformer.TransformableProfile; -import taboolib.library.xseries.reflection.XReflection; -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.mojang.authlib.GameProfile; -import org.bukkit.OfflinePlayer; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; - -import javax.annotation.Nonnull; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -/** - * Represents any object that has a {@link GameProfile} or one can be created with it. - * These objects are cached. - *

- * A {@link GameProfile} is an object that represents information about a Minecraft player's - * account in general (not specific to this or any other server) - * The most important information contained within this profile however, is the - * skin texture URL which the client needs to properly see the texture on items/blocks. - */ -public interface Profileable { - /** - * This method should not be used directly unless you know what you're doing. - *

- * The texture which might be cached. If any errors occur, the check may be re-evaluated. - * The cached values might also be re-evaluated due to expiration. - * @throws taboolib.library.xseries.profiles.exceptions.ProfileException may also throw other internal exceptions (most likely bugs) - * @return the original profile (not cloned if possible) for an instance that's always guaranteed to be a copy - * you can use {@link #getDisposableProfile()} instead. Null if no profile is set (only happens for {@link ProfileContainer}). - */ - @Nullable - @Unmodifiable - @ApiStatus.Internal - GameProfile getProfile(); - - /** - * Same as {@link #getProfile()}, except some implementations of {@link Profileable} - * cannot inherently return any original instance as they're not cacheable, so this - * method ensures that no duplicate cloning of {@link GameProfile} occurs for performance. - *

- * For most implementations however, this defaults to a simple cloning of the cached instances. - * @return always a copied version of {@link #getProfile()} that you can change. Null if {@link #getProfile()} is null - */ - @Nullable - @ApiStatus.Internal - default GameProfile getDisposableProfile() { - GameProfile profile = getProfile(); - return profile == null ? null : PlayerProfiles.clone(profile); - } - - /** - * Adds transformer (read {@link ProfileTransformer}) information to a copied version - * of this profile (so it doesn't affect this instance). - *

- * Profiles are copied before being transformed, so the main cache remains intact - * but the result of transformed profiles are never cached. - * @param transformers a list of transformers to apply in order once {@link #getProfile()} is called. - */ - default Profileable transform(ProfileTransformer... transformers) { - return new TransformableProfile(this, Arrays.asList(transformers)); - } - - /** - * A string representation of the {@link #getProfile()} which is useful for data storage. - * @return null if {@link #getProfile()} is null or the set profile doesn't have a texture property. - */ - @Nullable - default String getProfileValue() { - GameProfile profile = getProfile(); - return PlayerProfiles.getTextureProperty(profile).map(PlayerProfiles::getPropertyValue).orElse(null); - } - - @Nonnull - @ApiStatus.Experimental - static > CompletableFuture prepare(@Nonnull C profileables) { - return prepare(profileables, null, null); - } - - @Nonnull - @ApiStatus.Experimental - static > CompletableFuture prepare( - @Nonnull C profileables, @Nullable ProfileRequestConfiguration config, - @Nullable Function errorHandler) { - CompletableFuture> initial = CompletableFuture.completedFuture(new HashMap<>()); - List usernameRequests = new ArrayList<>(); - - if (!PlayerUUIDs.isOnlineMode()) { - for (Profileable profileable : profileables) { - String username = null; - if (profileable instanceof UsernameProfileable) { - username = ((UsernameProfileable) profileable).username; - } else if (profileable instanceof PlayerProfileable) { - username = ((PlayerProfileable) profileable).username; - } else if (profileable instanceof StringProfileable) { - if (((StringProfileable) profileable).determineType().type == ProfileInputType.USERNAME) { - username = ((StringProfileable) profileable).string; - } - } - - if (username != null) { - usernameRequests.add(username); - } - } - - if (!usernameRequests.isEmpty()) - initial = CompletableFuture.supplyAsync( - () -> MojangAPI.usernamesToUUIDs(usernameRequests, config), PlayerProfileFetcherThread.EXECUTOR); - } - - // First cache the username requests then get the profiles and finally return the original objects. - return XReflection.stacktrace(initial - .thenCompose(a -> { - List> requests = new ArrayList<>(profileables.size()); - - for (Profileable profileable : profileables) { - CompletableFuture async = CompletableFuture - .supplyAsync(profileable::getProfile, PlayerProfileFetcherThread.EXECUTOR); - - if (errorHandler != null) { - async = XReflection.stacktrace(async).exceptionally(ex -> { - boolean rethrow = errorHandler.apply(ex); - if (rethrow) throw XReflection.throwCheckedException(ex); - else return null; - }); - } - - requests.add(async); - } - - return CompletableFuture.allOf(requests.toArray(new CompletableFuture[0])); - }) - .thenApply((a) -> profileables)); - } - - /** - * Sets the skull texture based on the specified player UUID (whether it's an offline or online UUID). - */ - static Profileable username(String username) { - return new UsernameProfileable(username); - } - - /** - * Sets the skull texture based on the specified player UUID (whether it's an offline or online UUID). - */ - static Profileable of(UUID uuid) { - return new UUIDProfileable(uuid); - } - - /** - * Sets the skull texture based on the specified profile. - * If the profile already has textures, it will be used directly. Otherwise, a new profile will be fetched - * based on the UUID or username depending on the server's online mode. - * - * @param profile The profile to be used in the profile setting operation. - */ - static Profileable of(GameProfile profile) { - return new GameProfileProfileable(profile); - } - - /** - * Sets the skull texture based on the specified offline player. - * The profile lookup will depend on whether the server is running in online mode. - * - * @param offlinePlayer The offline player to generate the {@link GameProfile}. - */ - static Profileable of(OfflinePlayer offlinePlayer) { - return new PlayerProfileable(offlinePlayer); - } - - static Profileable of(BlockState blockState) { - return new ProfileContainer.BlockStateProfileContainer((Skull) blockState); - } - - static Profileable of(Block block) { - return new ProfileContainer.BlockProfileContainer(block); - } - - static Profileable of(ItemStack item) { - return new ProfileContainer.ItemStackProfileContainer(item); - } - - static Profileable of(ItemMeta meta) { - return new ProfileContainer.ItemMetaProfileContainer((SkullMeta) meta); - } - - /** - * Sets the skull texture based on a string. The input type is resolved based on the value provided. - * - *

Valid Types

- * Username: A player username. (e.g. Notch)
- * UUID: A player UUID. Offline or online mode UUID. (e.g. 069a79f4-44e9-4726-a5be-fca90e38aaf5)
- * Base64: The Base64 encoded value of textures JSON. (e.g. eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2NmNjc2N2RkMzQ3MzdlOTliZDU0YjY5NWVmMDY4M2M2YzZjZTZhNTRmNjZhZDk3Mjk5MmJkMGU0OGU0NTc5YiJ9fX0=)
- * Minecraft Textures URL: Check {@link ProfileInputType#TEXTURE_URL}.
- * Minecraft Textures Hash: Same as the URL, but only including the hash part, excluding the base URL. (e.g. e5461a215b325fbdf892db67b7bfb60ad2bf1580dc968a15dfb304ccd5e74db) - * - * @param input The input value used to retrieve the {@link GameProfile}. For more information check {@link ProfileInputType} - */ - static Profileable detect(String input) { - Objects.requireNonNull(input); - return new StringProfileable(input, null); - } - - /** - * Sets the skull texture based on a string with a known type. - * - * @param type The type of the input value. - * @param input The input value to generate the {@link GameProfile}. - */ - static Profileable of(ProfileInputType type, String input) { - Objects.requireNonNull(type, () -> "Cannot profile from a null input type: " + input); - Objects.requireNonNull(input, () -> "Cannot profile from a null input: " + type); - return new StringProfileable(input, type); - } - - final class UsernameProfileable extends TimedCacheableProfileable { - private final String username; - private Boolean valid; - - public UsernameProfileable(String username) {this.username = Objects.requireNonNull(username);} - - @Override - protected GameProfile getProfile0() { - if (valid == null) { - valid = ProfileInputType.USERNAME.pattern.matcher(username).matches(); - } - if (!valid) throw new InvalidProfileException("Invalid username: '" + username + '\''); - - Optional profileOpt = MojangAPI.getMojangCachedProfileFromUsername(username); - if (!profileOpt.isPresent()) - throw new UnknownPlayerException("Cannot find player named '" + username + '\''); - - GameProfile profile = profileOpt.get(); - if (PlayerProfiles.hasTextures(profile)) return profile; - return MojangAPI.getOrFetchProfile(profile); - } - } - - final class UUIDProfileable extends TimedCacheableProfileable { - private final UUID id; - - public UUIDProfileable(UUID id) {this.id = Objects.requireNonNull(id, "UUID cannot be null");} - - @Override - protected GameProfile getProfile0() { - GameProfile profile = MojangAPI.getCachedProfileByUUID(id); - if (PlayerProfiles.hasTextures(profile)) return profile; - return MojangAPI.getOrFetchProfile(profile); - } - } - - final class GameProfileProfileable extends TimedCacheableProfileable { - private final GameProfile profile; - - public GameProfileProfileable(GameProfile profile) {this.profile = Objects.requireNonNull(profile);} - - @Override - protected GameProfile getProfile0() { - if (PlayerProfiles.hasTextures(profile)) { - return profile; - } - - return (PlayerUUIDs.isOnlineMode() - ? new UUIDProfileable(profile.getId()) - : new UsernameProfileable(profile.getName()) - ).getProfile(); - } - } - - /** - * Do we need to support {@link org.bukkit.profile.PlayerProfile} for hybrid server software - * like Geyser or Mohist that might have their own caching system? - */ - final class PlayerProfileable extends TimedCacheableProfileable { - // Let the GC do its job. - @Nullable private final String username; - @Nonnull private final UUID id; - - public PlayerProfileable(OfflinePlayer player) { - Objects.requireNonNull(player); - this.username = player.getName(); - this.id = player.getUniqueId(); - } - - @Override - protected GameProfile getProfile0() { - // Can be empty if used by: - // CraftServer -> public OfflinePlayer getOfflinePlayer(UUID id) - if (Strings.isNullOrEmpty(username)) { - return new UUIDProfileable(id).getProfile(); - } else { - return new UsernameProfileable(username).getProfile(); - } - } - } - - final class StringProfileable extends TimedCacheableProfileable { - private final String string; - @Nullable private ProfileInputType type; - - public StringProfileable(String string, @Nullable ProfileInputType type) { - this.string = Objects.requireNonNull(string); - this.type = type; - } - - private StringProfileable determineType() { - if (type == null) type = ProfileInputType.typeOf(string); - return this; - } - - @Override - protected GameProfile getProfile0() { - determineType(); - if (type == null) { - throw new InvalidProfileException("Unknown skull string value: " + string); - } - return type.getProfile(string); - } - } -} \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/CacheableProfileable.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/CacheableProfileable.java deleted file mode 100644 index 13ca6b81a..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/CacheableProfileable.java +++ /dev/null @@ -1,70 +0,0 @@ -package taboolib.library.xseries.profiles.objects.cache; - -import taboolib.library.xseries.profiles.exceptions.MojangAPIRetryException; -import taboolib.library.xseries.profiles.objects.Profileable; -import taboolib.library.xseries.reflection.XReflection; -import com.mojang.authlib.GameProfile; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * Any {@link Profileable} that can have its results cached. - * This class should not be used directly. - */ -@ApiStatus.Internal -public abstract class CacheableProfileable implements Profileable { - protected GameProfile cache; - protected Throwable lastError; - - @Override - public final synchronized GameProfile getProfile() { - // Synchronized in case two threads try to access the - // same profileable that is not cached yet. That way, other threads - // will wait for the first one to cache the results so the other threads - // can start accessing the cache instantly instead of sending multiple - // requests for the same data. - // This of course doesn't fix the issue if two separate Profileables - // are used for a single value and both are somehow requested at the same time. - if (hasExpired(true)) { - lastError = null; - cache = null; - } - - if (lastError != null) { - if (!(lastError instanceof MojangAPIRetryException)) { - throw XReflection.throwCheckedException(lastError); - } - } - - if (cache == null) { - try { - cache = getProfile0(); - lastError = null; - } catch (Throwable ex) { - lastError = ex; - throw ex; - } - } - - return cache; - } - - /** - * @return true if this profile hasn't been cached yet or the cache is expired. - */ - public final boolean hasExpired() { - return hasExpired(false); - } - - protected boolean hasExpired(boolean renew) { - return lastError instanceof MojangAPIRetryException; - } - - @NotNull - protected abstract GameProfile getProfile0(); - - @Override - public final String toString() { - return this.getClass().getSimpleName() + "[cache=" + cache + ", lastError=" + lastError + ']'; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/TimedCacheableProfileable.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/TimedCacheableProfileable.java deleted file mode 100644 index 78cd5a9ed..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/cache/TimedCacheableProfileable.java +++ /dev/null @@ -1,52 +0,0 @@ -package taboolib.library.xseries.profiles.objects.cache; - -import taboolib.library.xseries.profiles.objects.Profileable; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.time.Duration; - -/** - * Any {@link Profileable} that can have its results cached temporarily. - * This class should not be used directly. - */ -@ApiStatus.Internal -public abstract class TimedCacheableProfileable extends CacheableProfileable { - private long lastUpdate; - - /** - * The amount of time the cached results of this profile can be used until it's re-evaluated. - * By default, it uses the internal cache's expiration date (6 hours) - * {@link Duration#ZERO} or negative durations should not be used. - */ - @NotNull - protected Duration expiresAfter() { - return Duration.ofHours(6); - } - - /** - * @return true if this profile hasn't been cached yet or the cache is expired. - */ - @Override - public final boolean hasExpired(boolean renew) { - if (super.hasExpired(renew)) return true; - if (cache == null && lastError == null) return true; - - Duration expiresAfter = expiresAfter(); - if (expiresAfter.isZero()) return false; - - long now = System.currentTimeMillis(); - if (lastUpdate == 0) { - if (renew) lastUpdate = now; - return true; - } else { - long diff = now - lastUpdate; - if (diff >= expiresAfter.toMillis()) { - if (renew) lastUpdate = now; - return true; - } else { - return false; - } - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/ProfileTransformer.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/ProfileTransformer.java deleted file mode 100644 index d36576c5e..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/ProfileTransformer.java +++ /dev/null @@ -1,117 +0,0 @@ -package taboolib.library.xseries.profiles.objects.transformer; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * Modifies a {@link GameProfile} by a set of defined operations. - * Transformers can be composed and should be applied in order. - */ -public interface ProfileTransformer { - /** - * Transforms the given profile by the defined operations. - * The operations are applied on the same instance of the profile. - * @param profile the profile to apply the operations on. - * @return may return the same or a new instance. - */ - @NotNull - GameProfile transform(@NotNull GameProfile profile); - - /** - * Whether the results of this transformation can be cached or not. - */ - @ApiStatus.Internal - boolean canBeCached(); - - /** - * By default, due to internal changes to the {@link GameProfile}, - * (specially the Base64-encoded textures property) the items are - * considered "different" (for example {@link org.bukkit.inventory.ItemStack#isSimilar(ItemStack)} - * would return false) - * which means they cannot be stacked together in the game. - *

- * These changes can include: - *

    - *
  • Mojang's cache/API might add/remove properties from the profile.
  • - *
  • Mojang's API adds a timestamp property that shows when was the last - * time the profile was updated. This can be frequent or take up to hours to change.
  • - *
  • Mojang's cache/API might change the order or format of the textures JSON.
  • - *
  • Players that change a portion of their skin (other than the head) will - * still be visually the same, but their texture URL will be changed.
  • - *
  • Profiles created/modified by XSeries have a special signature added.
  • - *
- *
- * This transformer, makes the items stackable (x64 stack) - * This currently has no observable effect on blocks. - * Technically the same as {@link #removeMetadata()} but it's - * here for future compatibility purposes. - * @see #nonStackable() - */ - static ProfileTransformer stackable() { - return RemoveMetadata.INSTANCE; - } - - /** - * Makes the items non-stackable (x64 stack) - * Read {@link #stackable()} for more info. This adds additional properties other than - * the ones mentioned in the other method that ensures items can never be stacked no - * matter how similar they are. - * @see #stackable() - */ - static ProfileTransformer nonStackable() { - return MakeNotStackable.INSTANCE; - } - - /** - * Removes extra properties from this profile. - * This includes the XSeries signature and the timestamp of the profile. - * Both which have no effect when removed, but the XSeries signature could - * be useful in debugging. - * - * @see #stackable() - */ - static ProfileTransformer removeMetadata() { - return RemoveMetadata.INSTANCE; - } - - final class MakeNotStackable implements ProfileTransformer { - private static final MakeNotStackable INSTANCE = new MakeNotStackable(); - private static final String PROPERTY_NAME = "XSeriesSeed"; - private static final AtomicLong NEXT_ID = new AtomicLong(); - - @Override - public GameProfile transform(GameProfile profile) { - String value = System.currentTimeMillis() + "-" + NEXT_ID.getAndIncrement(); - profile.getProperties().put(PROPERTY_NAME, new Property(PROPERTY_NAME, value)); - return profile; - } - - @Override - public boolean canBeCached() { - return false; - } - } - - final class RemoveMetadata implements ProfileTransformer { - private static final RemoveMetadata INSTANCE = new RemoveMetadata(); - - @Override - public GameProfile transform(GameProfile profile) { - PlayerProfiles.removeTimestamp(profile); - // It's a multimap, remove all values associated to this key. - profile.getProperties().asMap().remove(PlayerProfiles.DEFAULT_PROFILE_NAME); - return profile; - } - - @Override - public boolean canBeCached() { - return true; - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/TransformableProfile.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/TransformableProfile.java deleted file mode 100644 index 40df8da88..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/profiles/objects/transformer/TransformableProfile.java +++ /dev/null @@ -1,80 +0,0 @@ -package taboolib.library.xseries.profiles.objects.transformer; - -import taboolib.library.xseries.profiles.PlayerProfiles; -import taboolib.library.xseries.profiles.objects.Profileable; -import com.mojang.authlib.GameProfile; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public final class TransformableProfile implements Profileable { - private final Profileable profileable; - private final TransformationSequence transformers; - - public TransformableProfile(Profileable profileable, List transformers) { - this.profileable = profileable; - this.transformers = new TransformationSequence(transformers); - } - - private static final class TransformationSequence { - @Nullable - private GameProfile profile; - private boolean expired, markRestAsCopy; - private final TransformedProfileCache[] transformers; - - private TransformationSequence(List transformers) { - this.transformers = transformers.stream() - .map(TransformedProfileCache::new) - .toArray(TransformedProfileCache[]::new); - } - - private final class TransformedProfileCache { - @Nullable - private final ProfileTransformer transformer; - @Nullable - private GameProfile cacheProfile; - - private TransformedProfileCache(@Nullable ProfileTransformer transformer) {this.transformer = transformer;} - - private void transform() { - if (cacheProfile != null && transformer.canBeCached()) { - if (!expired) { - profile = cacheProfile; - return; - } - } else { - expired = true; - } - profile = cacheProfile = transformer.transform(markRestAsCopy ? profile : PlayerProfiles.clone(profile)); - if (!transformer.canBeCached()) markRestAsCopy = true; - } - } - } - - @Override - public Profileable transform(ProfileTransformer... transformers) { - // Return a new instance because we promised not to affect the current instance for transform() method. - List transformersList = new ArrayList<>(this.transformers.transformers.length + transformers.length); - transformersList.addAll(Arrays.stream(this.transformers.transformers).map(x -> x.transformer).collect(Collectors.toList())); - transformersList.addAll(Arrays.asList(transformers)); - return new TransformableProfile(profileable, transformersList); - } - - @Override - public GameProfile getProfile() { - // This method doesn't need to be synchronized for the cache (see CacheableProfileable#getProfile), since the - // transformation sequences don't send any API requests. The cost of synchronizing - // this method would be probably more than letting the transformation happen again. - transformers.profile = profileable.getProfile(); - if (transformers.profile == null) return null; - - for (TransformationSequence.TransformedProfileCache transformer : transformers.transformers) { - transformer.transform(); - } - - return transformers.profile; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/AggregateReflectiveHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/AggregateReflectiveHandle.java deleted file mode 100644 index 21e7b7a58..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/AggregateReflectiveHandle.java +++ /dev/null @@ -1,94 +0,0 @@ -package taboolib.library.xseries.reflection; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.function.Consumer; - -/** - * Most reflection APIs already provide a name-based fallback system that can be used (like {@link taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle#named(String...)}) - * but sometimes the signature of certain JVM declarations just change entirely, that's where this class could be useful. - *

- * It simply tries the {@link #reflect()} method of each handle until one of them returns without throwing an exception (order matters). - *

Usage

- *
{@code
- *    MethodMemberHandle profileByName = XReflection.of(GameProfileCache.class).method().named("getProfile", "a");
- *    MethodHandle getProfileByName = XReflection.anyOf(
- *        () -> profileByName.signature("public GameProfile get(String username);"),
- *        () -> profileByName.signature("public Optional get(String username);")
- *    ).reflect();
- * }
- * @param the JVM type of {@link H} - * @param the types of handles that are going to be checked. - * @see ReflectiveHandle - */ -public class AggregateReflectiveHandle> implements ReflectiveHandle { - private final List> handles; - private Consumer handleModifier; - - protected AggregateReflectiveHandle(Collection> handles) { - this.handles = new ArrayList<>(handles.size()); - this.handles.addAll(handles); - } - - /** - * @see #or(Callable) - */ - public AggregateReflectiveHandle or(@Nonnull H handle) { - return or(() -> handle); - } - - /** - * @see #or(ReflectiveHandle) - */ - public AggregateReflectiveHandle or(@Nonnull Callable handle) { - this.handles.add(handle); - return this; - } - - /** - * Action performed on all the handles before being checked. - */ - public AggregateReflectiveHandle modify(@Nullable Consumer handleModifier) { - this.handleModifier = handleModifier; - return this; - } - - @SuppressWarnings("MethodDoesntCallSuperMethod") - @Override - public AggregateReflectiveHandle clone() { - AggregateReflectiveHandle handle = new AggregateReflectiveHandle<>(new ArrayList<>(handles)); - handle.handleModifier = this.handleModifier; - return handle; - } - - @Override - public T reflect() throws ReflectiveOperationException { - ClassNotFoundException errors = null; - - for (Callable handle : handles) { - H handled; - try { - handled = handle.call(); - } catch (Throwable ex) { - if (errors == null) - errors = new ClassNotFoundException("None of the aggregate handles were successful"); - errors.addSuppressed(ex); - continue; - } - if (handleModifier != null) handleModifier.accept(handled); - try { - return handled.reflect(); - } catch (Throwable ex) { - if (errors == null) - errors = new ClassNotFoundException("None of the aggregate handles were successful"); - errors.addSuppressed(ex); - } - } - - throw XReflection.relativizeSuppressedExceptions(errors); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveHandle.java deleted file mode 100644 index aa1617ec3..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveHandle.java +++ /dev/null @@ -1,91 +0,0 @@ -package taboolib.library.xseries.reflection; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import javax.annotation.Nonnull; - -/** - * Represents an object that can be used for reflection operations. - * @param The JVM type associated with this handle (e.g. {@link Class}, {@link java.lang.reflect.Field}, - * {@link java.lang.reflect.Method}, or {@link java.lang.reflect.Constructor}) - * @see AggregateReflectiveHandle - * @see taboolib.library.xseries.reflection.jvm.classes.ClassHandle - * @see taboolib.library.xseries.reflection.jvm.MethodMemberHandle - * @see taboolib.library.xseries.reflection.jvm.FieldMemberHandle - * @see taboolib.library.xseries.reflection.jvm.ConstructorMemberHandle - */ -public interface ReflectiveHandle extends Cloneable { - /** - * Creates a new handle with the same properties. - */ - @ApiStatus.Experimental - ReflectiveHandle clone(); - - /** - * @return true if this object exists at runtime, otherwise false. - */ - default boolean exists() { - try { - reflect(); - return true; - } catch (ReflectiveOperationException ignored) { - return false; - } - } - - /** - * Catches any {@link ReflectiveOperationException} thrown by {@link #reflect()} - * @deprecated I don't think there's going to be any practical use for this method. - */ - @Deprecated - @Nullable - default ReflectiveOperationException catchError() { - try { - reflect(); - return null; - } catch (ReflectiveOperationException ex) { - return ex; - } - } - - /** - * An unchecked exception version of {@link #reflect()} (throws the original exception, not the a {@link RuntimeException}) - * @see #reflect() - * @see #reflectOrNull() - * @throws ReflectiveOperationException throws silently. - */ - @SuppressWarnings("JavadocDeclaration") - @Nonnull - default T unreflect() { - try { - return reflect(); - } catch (ReflectiveOperationException e) { - throw XReflection.throwCheckedException(e); - } - } - - /** - * Same as {@link #reflect()} except that it will return null if any {@link ReflectiveOperationException} errors occur. - * @see #reflect() - * @see #unreflect() - */ - @Nullable - default T reflectOrNull() { - try { - return reflect(); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - /** - * The heart of the class. - * @return the final JVM object that exists in this runtime. - * @throws ReflectiveOperationException if any errors occur while getting the object, including unknown objects, security issues, etc... - * @see #reflectOrNull() - * @see #unreflect() - */ - @Nonnull - T reflect() throws ReflectiveOperationException; -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveMapping.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveMapping.java deleted file mode 100644 index 2856cebe8..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveMapping.java +++ /dev/null @@ -1,15 +0,0 @@ -package taboolib.library.xseries.reflection; - -import taboolib.library.xseries.reflection.jvm.NamedReflectiveHandle; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -public interface ReflectiveMapping { - boolean shouldBeChecked(); - - String category(); - - String name(); - - String process(NamedReflectiveHandle handle, String name); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveNamespace.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveNamespace.java deleted file mode 100644 index 00ef05dc9..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/ReflectiveNamespace.java +++ /dev/null @@ -1,141 +0,0 @@ -package taboolib.library.xseries.reflection; - -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.StaticClassHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftClassHandle; -import taboolib.library.xseries.reflection.parser.ReflectionParser; -import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import java.lang.invoke.MethodHandles; -import java.util.*; - -/** - * This class is mostly only useful if you're planning to use string-based API (see {@link ReflectionParser}), - * other than that most of the work is done behind the scenes, and it's not needed to use it directly. - * You can initiate this class using {@link XReflection#namespaced()}. Like other reflection classes, this should - * be used as a temporary class and you should cache the results of {@link ReflectiveHandle} classes. - *

- * This class is used just like {@link taboolib.library.xseries.reflection.XReflection} except that it - * allows enhanced performance and security checks. Performance because a single reflection lookup - * object is used for all reflections that use this namespace, security because of the same lookup - * object, the {@link MethodHandles#lookup()} which is caller sensitive. - *

- * This class also provides an import statement feature. For example look at the following code: - *

{@code
- *     XReflection.of(Test.class).method("public List getNames();").unreflect();
- * }
- * Assuming that the class and the method exist, this works, it knows about the "List" type from {@link java.util.List} - * because it's predefined and hardcoded. But if we look at another code: - *
{@code
- *      XReflection.of(Test.class).method("public MyCustomClass getCustomData();").unreflect();
- * }
- * This will fail, because it doesn't know where {@code MyCustomClass} is, you could give it the fully qualified name: - *
{@code
- *      XReflection.of(Test.class).method("public my.package.MyCustomClass getCustomData();").unreflect();
- * }
- * But what if you want to keep using this type a lot? It makes the code look very ugly. That is what this class is for: - *
{@code
- *      ReflectiveNamespace ns = XReflection.namespaced().imports(MyCustomClass.class);
- *      ns.of(Test.class).method("public MyCustomClass getCustomData();").unreflect();
- * }
- *
- * Also, all the types that are passed to or parsed from this namespace - * (e.g. from {@link #of(Class)}, {@link #ofMinecraft(String)}, {@link #classHandle(String)}) - * are imported automatically. Making this a powerful mini-IDE! - *


- * Note that sometimes you need to import remapped classes manually if you're going to be using {@link #of(Class)} - * since these class names are going to be remapped at runtime, and if you use the obfuscated names in - * string-based API signatures, it'll fail: - *

{@code
- *      ReflectiveNamespace ns = XReflection.namespaced();
- *
- *      // If you don't do the following, then this whole thing won't work.
- *      // The reason why adding this manual import works is because "MinecraftKey" class
- *      // will be remapped to "ResourceLocation" at runtime, so the following code will be
- *      // translated to ns.imports("MinecraftKey", ResourceLocation.class);
- *      ns.imports("MinecraftKey", MinecraftKey.class);
- *
- *      // Alternatively you could just use "ResourceLocation" instead of "MinecraftKey" here in the string.
- *      ns.of(MinecraftKey.class).method("public static MinecraftKey fromNamespaceAndPath(String namespace, String path);").unreflect();
- * }
- */ -public class ReflectiveNamespace { - private final Map> imports = new HashMap<>(); - private final MethodHandles.Lookup lookup = MethodHandles.lookup(); - private final Set handles = Collections.newSetFromMap(new IdentityHashMap<>()); - - protected ReflectiveNamespace() {} - - /** - * Imports the specified classes into this namespace so the names can be used directly. - * (For more info read {@link ReflectiveNamespace}. - *

- * This can also override predefined Java standard types if the names are the same. - */ - public ReflectiveNamespace imports(@Nonnull Class... classes) { - for (Class clazz : classes) { - imports(clazz.getSimpleName(), clazz); - } - return this; - } - - /** - * Imports a class with a custom name. - * @param name the custom name of the class. - * @param clazz the actual definition of the class. - * @see #imports(Class[]) - */ - public ReflectiveNamespace imports(@Nonnull String name, @Nonnull Class clazz) { - Objects.requireNonNull(name); - Objects.requireNonNull(clazz); - this.imports.put(name, clazz); - return this; - } - - @Nonnull - @ApiStatus.Internal - public Map> getImports() { - for (ClassHandle handle : handles) { - Class clazz = handle.reflectOrNull(); - if (clazz == null) continue; - - for (String className : handle.getPossibleNames()) { - this.imports.put(className, clazz); - } - } - return this.imports; - } - - @Nonnull - @ApiStatus.Internal - public MethodHandles.Lookup getLookup() { - return lookup; - } - - /** - * @since v11.0.0 - */ - public StaticClassHandle of(Class clazz) { - imports(clazz); - return new StaticClassHandle(this, clazz); - } - - @ApiStatus.Internal - public void link(ClassHandle handle) { - if (handle.getNamespace() != this) throw new IllegalArgumentException("Not the same namespace"); - this.handles.add(handle); - } - - public DynamicClassHandle classHandle(@Language("Java") String declaration) { - DynamicClassHandle classHandle = new DynamicClassHandle(this); - return new ReflectionParser(declaration).imports(this).parseClass(classHandle); - } - - public MinecraftClassHandle ofMinecraft(@Language("Java") String declaration) { - MinecraftClassHandle classHandle = new MinecraftClassHandle(this); - return new ReflectionParser(declaration).imports(this).parseClass(classHandle); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/VersionHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/VersionHandle.java deleted file mode 100644 index d50f45016..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/VersionHandle.java +++ /dev/null @@ -1,98 +0,0 @@ -package taboolib.library.xseries.reflection; - -import java.util.concurrent.Callable; - -/** - * Provides objects based on the current version of the server ({@link XReflection#supports(int, int, int)}), - * it's mostly useful for primitive values, for reflection you should use the specialized - * {@link XReflection} class instead. - * @param the type of object to check. - */ -public final class VersionHandle { - private int version, patch; - private T handle; - // private RuntimeException errors; - - VersionHandle(int version, T handle) { - this(version, 0, handle); - } - - VersionHandle(int version, int patch, T handle) { - if (XReflection.supports(version, patch)) { - this.version = version; - this.patch = patch; - this.handle = handle; - } - } - - public VersionHandle(int version, int patch, Callable handle) { - if (XReflection.supports(version, patch)) { - this.version = version; - this.patch = patch; - - try { - this.handle = handle.call(); - } catch (Exception ignored) { - } - } - } - - public VersionHandle(int version, Callable handle) { - this(version, 0, handle); - } - - public VersionHandle v(int version, T handle) { - return v(version, 0, handle); - } - - private boolean checkVersion(int version, int patch) { - if (version == this.version && patch == this.patch) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version + '.' + patch); - return version > this.version && patch >= this.patch && XReflection.supports(version, patch); - } - - public VersionHandle v(int version, int patch, Callable handle) { - if (!checkVersion(version, patch)) return this; - - try { - this.handle = handle.call(); - } catch (Exception ignored) { - } - - this.version = version; - this.patch = patch; - return this; - } - - public VersionHandle v(int version, int patch, T handle) { - if (checkVersion(version, patch)) { - this.version = version; - this.patch = patch; - this.handle = handle; - } - return this; - } - - /** - * If none of the previous version checks matched, it'll return this object. - * @see #orElse(Callable) - */ - public T orElse(T handle) { - return this.version == 0 ? handle : this.handle; - } - - /** - * If none of the previous version checks matched, it'll return this object. - * @see #orElse(Object) - */ - public T orElse(Callable handle) { - if (this.version == 0) { - try { - return handle.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return this.handle; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/XReflection.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/XReflection.java deleted file mode 100644 index b1f973a70..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/XReflection.java +++ /dev/null @@ -1,654 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2024 Crypto Morin - * - * 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 taboolib.library.xseries.reflection; - -import taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.StaticClassHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftClassHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import taboolib.library.xseries.reflection.minecraft.MinecraftPackage; -import org.bukkit.Bukkit; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * General Java reflection handler, but specialized for Minecraft NMS/CraftBukkit reflection as well. - *

- *

Starting Points

- * Basic reflection starting points are through the {@link taboolib.library.xseries.reflection.jvm.classes.ClassHandle} - * methods: - *
    - *
  • {@link #of(Class)}: For static classes with known type at compile time.
  • - *
  • {@link #classHandle()}: For general classes that have unknown type at compile time.
  • - *
  • {@link #ofMinecraft()}: Specialized for Minecraft-related classes.
  • - *
  • {@link #namespaced()}: String-based API for getting classes with Java code inside strings that is more readable.
  • - *
- *

Fallback

- * Some methods exist to choose between different values depending on the situation: - *
    - *
  • {@link #v(int, Object)}: Basic Minecraft version-based value handler.
  • - *
  • {@link #any(ReflectiveHandle[])} and {@link #anyOf(Callable[])}: Advanced fallback-based support for all the reflection operations.
  • - *
- *

Others

- * Also, there are a few other non-reflection APIs in this class that are a bit "hacky" which is why they're here. - *
    - *
  • {@link #getVersionInformation()}: Useful string to include in your reflection related errors.
  • - *
  • {@link #throwCheckedException(Throwable)}: Force throw checked exceptions as unchecked.
  • - *
  • {@link #stacktrace(CompletableFuture)}: Add stacktrace information to {@link CompletableFuture}s.
  • - *
  • {@link #relativizeSuppressedExceptions(Throwable)}: Relativize the stacktrace of exceptions that are thrown from the same location.
  • - *
- * @author Crypto Morin - * @version 11.2.1 - * @see taboolib.library.xseries.reflection.minecraft.MinecraftConnection - * @see taboolib.library.xseries.reflection.minecraft.NMSExtras - */ -public final class XReflection { - /** - * We use reflection mainly to avoid writing a new class for version barrier. - * The version barrier is for NMS that uses the Minecraft version as the main package name. - *

- * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} - * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} - * In order to maintain cross-version compatibility we cannot import these classes. - *

- * Performance is not a concern for these specific statically initialized values. - *

- * Versions Legacy - *

- * This will no longer work because of - * Paper no-relocation - * strategy. - */ - @Nullable - @ApiStatus.Internal - public static final String NMS_VERSION = findNMSVersionString(); - - /** - * The current version of XSeries. Mostly used for the {@link taboolib.library.xseries.profiles.builder.XSkull} API. - */ - @ApiStatus.Internal - public static final String XSERIES_VERSION = "11.2.0"; - - @Nullable - @ApiStatus.Internal - public static String findNMSVersionString() { - // This needs to be right below VERSION because of initialization order. - // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() - // which allows easier testing as well. - String found = null; - for (Package pack : Package.getPackages()) { - String name = pack.getName(); - - // .v because there are other packages. - if (name.startsWith("org.bukkit.craftbukkit.v")) { - found = pack.getName().split("\\.")[3]; - - // Just a final guard to make sure it finds this important class. - // As a protection for forge+bukkit implementation that tend to mix versions. - // The real CraftPlayer should exist in the package. - // Note: Doesn't seem to function properly. Will need to separate the version - // handler for NMS and CraftBukkit for software like catmc. - try { - Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); - break; - } catch (ClassNotFoundException e) { - found = null; - } - } - } - - return found; - } - - public static final int MAJOR_NUMBER; - /** - * The raw minor version number. - * E.g. {@code v1_17_R1} to {@code 17} - * - * @see #supports(int) - * @since 4.0.0 - */ - public static final int MINOR_NUMBER; - /** - * The raw patch version number. Refers to the major.minor.patch version scheme. - * E.g. - *

    - *
  • {@code v1.20.4} to {@code 4}
  • - *
  • {@code v1.18.2} to {@code 2}
  • - *
  • {@code v1.19.1} to {@code 1}
  • - *
- *

- * I'd not recommend developers to support individual patches at all. You should always support the latest patch. - * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 - *

- * This can be used to warn server owners when your plugin will break on older patches. - * - * @see #supportsPatch(int) - * @since 7.0.0 - */ - public static final int PATCH_NUMBER; - - static { - /* Old way of doing this. - String[] split = NMS_VERSION.substring(1).split("_"); - if (split.length < 1) { - throw new IllegalStateException("Version number division error: " + Arrays.toString(split) + ' ' + getVersionInformation()); - } - - String minorVer = split[1]; - try { - MINOR_NUMBER = Integer.parseInt(minorVer); - if (MINOR_NUMBER < 0) - throw new IllegalStateException("Negative minor number? " + minorVer + ' ' + getVersionInformation()); - } catch (Throwable ex) { - throw new RuntimeException("Failed to parse minor number: " + minorVer + ' ' + getVersionInformation(), ex); - } - */ - - // NMS_VERSION = v1_20_R3 - // Bukkit.getBukkitVersion() = 1.20.4-R0.1-SNAPSHOT - // Bukkit.getVersion() = git-Paper-364 (MC: 1.20.4) - Matcher bukkitVer = Pattern - // is optional for first releases like "1.8-R0.1-SNAPSHOT" - .compile("^(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?") - .matcher(Bukkit.getBukkitVersion()); - if (bukkitVer.find()) { // matches() won't work, we just want to match the start using "^" - try { - // group(0) gives the whole matched string, we just want the captured group. - String patch = bukkitVer.group("patch"); - MAJOR_NUMBER = Integer.parseInt(bukkitVer.group("major")); - MINOR_NUMBER = Integer.parseInt(bukkitVer.group("minor")); - PATCH_NUMBER = Integer.parseInt((patch == null || patch.isEmpty()) ? "0" : patch); - } catch (Throwable ex) { - throw new RuntimeException("Failed to parse minor number: " + bukkitVer + ' ' + getVersionInformation(), ex); - } - } else { - throw new IllegalStateException("Cannot parse server version: \"" + Bukkit.getBukkitVersion() + '"'); - } - } - - /** - * Gets the full version information of the server. Useful for including in errors. - * "NMS" might return "Unknown NMS", which means that they're running a Paper - * server that removed the CraftBukkit NMS version guard. - * - * @since 7.0.0 - */ - public static String getVersionInformation() { - // Bukkit.getServer().getMinecraftVersion() is for Paper - return "(NMS: " + (NMS_VERSION == null ? "Unknown NMS" : NMS_VERSION) + " | " + - "Parsed: " + MAJOR_NUMBER + '.' + MINOR_NUMBER + '.' + PATCH_NUMBER + " | " + - "Minecraft: " + Bukkit.getVersion() + " | " + - "Bukkit: " + Bukkit.getBukkitVersion() + ')'; - } - - /** - * Gets the latest known patch number of the given minor version. - * For example: 1.14 -> 4, 1.17 -> 10 - * The latest version is expected to get newer patches, so make sure to account for unexpected results. - * - * @param minorVersion the minor version to get the patch number of. - * @return the patch number of the given minor version if recognized, otherwise null. - * @since 7.0.0 - */ - @Nullable - public static Integer getLatestPatchNumberOf(int minorVersion) { - if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion); - - // https://minecraft.wiki/w/Java_Edition_version_history - // There are many ways to do this, but this is more visually appealing. - int[] patches = { - /* 1 */ 1, - /* 2 */ 5, - /* 3 */ 2, - /* 4 */ 7, - /* 5 */ 2, - /* 6 */ 4, - /* 7 */ 10, - /* 8 */ 8, // I don't think they released a server version for 1.8.9 - /* 9 */ 4, - - /* 10 */ 2,// ,_ _ _, - /* 11 */ 2,// \o-o/ - /* 12 */ 2,// ,(.-.), - /* 13 */ 2,// _/ |) (| \_ - /* 14 */ 4,// /\=-=/\ - /* 15 */ 2,// ,| \=/ |, - /* 16 */ 5,// _/ \ | / \_ - /* 17 */ 1,// \_!_/ - /* 18 */ 2, - /* 19 */ 4, - /* 20 */ 6, - /* 21 */ 1, - }; - - if (minorVersion > patches.length) return null; - return patches[minorVersion - 1]; - } - - /** - * Mojang remapped their NMS in 1.17: Spigot Thread - */ - @ApiStatus.Internal - public static final String - CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName(), - NMS_PACKAGE = v(17, "net.minecraft").orElse("net.minecraft.server." + NMS_VERSION); - - @ApiStatus.Internal - @ApiStatus.Experimental - public static final Set SUPPORTED_MAPPINGS; - - static { - if (ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.level") - .map(MinecraftMapping.MOJANG, "ServerPlayer") - .exists()) { - SUPPORTED_MAPPINGS = EnumSet.of(MinecraftMapping.MOJANG); - } else if (ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.level") - .map(MinecraftMapping.MOJANG, "EntityPlayer") - .exists()) { - SUPPORTED_MAPPINGS = EnumSet.of(MinecraftMapping.SPIGOT, MinecraftMapping.OBFUSCATED); - } else { - MinecraftClassHandle entityPlayer = ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.level") - .map(MinecraftMapping.MOJANG, "ServerPlayer") - .map(MinecraftMapping.SPIGOT, "EntityPlayer"); - - throw new RuntimeException("Unknown Minecraft mapping " + getVersionInformation(), entityPlayer.catchError()); - } - } - - private XReflection() {} - - /** - * Gives the {@code handle} object if the server version is equal or greater than the given version. - * This method is purely for readability and should be always used with {@link VersionHandle#orElse(Object)}. - * - * @see #v(int, int, Object) - * @see VersionHandle#orElse(Object) - * @since 5.0.0 - */ - public static VersionHandle v(int version, T handle) { - return new VersionHandle<>(version, handle); - } - - /** - * @since 9.5.0 - */ - public static VersionHandle v(int version, int patch, T handle) { - return new VersionHandle<>(version, patch, handle); - } - - public static VersionHandle v(int version, Callable handle) { - return new VersionHandle<>(version, handle); - } - - public static VersionHandle v(int version, int patch, Callable handle) { - return new VersionHandle<>(version, patch, handle); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param minorNumber the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #MINOR_NUMBER - * @since 4.0.0 - */ - public static boolean supports(int minorNumber) { - return MINOR_NUMBER >= minorNumber; - } - - /** - * A more friendly version of {@link #supports(int, int)} for people with OCD. - */ - public static boolean supports(int majorNumber, int minorNumber, int patchNumber) { - if (majorNumber != 1) throw new IllegalArgumentException("Invalid major number: " + majorNumber); - return supports(minorNumber, patchNumber); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param minorNumber the minor version to compare the server version with. - * @param patchNumber the patch number to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #MINOR_NUMBER - * @see #PATCH_NUMBER - * @since 7.1.0 - */ - public static boolean supports(int minorNumber, int patchNumber) { - return MINOR_NUMBER == minorNumber ? PATCH_NUMBER >= patchNumber : supports(minorNumber); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param patchNumber the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #PATCH_NUMBER - * @since 7.0.0 - * @deprecated use {@link #supports(int, int)} - */ - @Deprecated - public static boolean supportsPatch(int patchNumber) { - return PATCH_NUMBER >= patchNumber; - } - - /** - * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. - * - * @param packageName the 1.17+ package name of this class. - * @param name the name of the class. - * @return the NMS class or null if not found. - * @throws RuntimeException if the class could not be found. - * @deprecated use {@link #ofMinecraft()} instead. - * @see #getNMSClass(String) - * @since 4.0.0 - */ - @Nonnull - @Deprecated - public static Class getNMSClass(@Nullable String packageName, @Nonnull String name) { - if (packageName != null && supports(17)) name = packageName + '.' + name; - - try { - return Class.forName(NMS_PACKAGE + '.' + name); - } catch (ClassNotFoundException ex) { - throw new RuntimeException(ex); - } - } - - /** - * Get a NMS {@link #NMS_PACKAGE} class. - * - * @param name the name of the class. - * @return the NMS class or null if not found. - * @throws RuntimeException if the class could not be found. - * @see #getNMSClass(String, String) - * @since 1.0.0 - * @deprecated use {@link #ofMinecraft()} - */ - @Nonnull - @Deprecated - public static Class getNMSClass(@Nonnull String name) { - return getNMSClass(null, name); - } - - /** - * Get a CraftBukkit (org.bukkit.craftbukkit) class. - * - * @param name the name of the class to load. - * @return the CraftBukkit class or null if not found. - * @throws RuntimeException if the class could not be found. - * @since 1.0.0 - * @deprecated use {@link #ofMinecraft()} instead. - */ - @Nonnull - @Deprecated - public static Class getCraftClass(@Nonnull String name) { - try { - return Class.forName(CRAFTBUKKIT_PACKAGE + '.' + name); - } catch (ClassNotFoundException ex) { - throw new RuntimeException(ex); - } - } - - /** - * Gives an array version of a class. For example if you wanted {@code EntityPlayer[]} you'd use: - *

{@code
-     *     Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
-     *     Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
-     * }
- *

- * Note that this doesn't work on primitive classes. - * - * @param clazz the class to get the array version of. You could use for multi-dimensions arrays too. - * @throws RuntimeException if the class could not be found. - */ - @Nonnull - public static Class toArrayClass(Class clazz) { - try { - return Class.forName("[L" + clazz.getName() + ';'); - } catch (ClassNotFoundException ex) { - throw new RuntimeException("Cannot find array class for class: " + clazz, ex); - } - } - - /** - * @since v9.0.0 - */ - public static MinecraftClassHandle ofMinecraft() { - return new MinecraftClassHandle(new ReflectiveNamespace()); - } - - /** - * @since v9.0.0 - */ - public static DynamicClassHandle classHandle() { - return new DynamicClassHandle(new ReflectiveNamespace()); - } - - /** - * @since v11.0.0 - */ - public static StaticClassHandle of(Class clazz) { - return new StaticClassHandle(new ReflectiveNamespace(), clazz); - } - - - /** - * Read {@link ReflectiveNamespace} for more info. - * @since v11.0.0 - */ - public static ReflectiveNamespace namespaced() { - return new ReflectiveNamespace(); - } - - /** - * @since v9.0.0 - */ - @SafeVarargs - public static > AggregateReflectiveHandle any(H... handles) { - return new AggregateReflectiveHandle<>(Arrays.stream(handles).map(x -> (Callable) () -> x).collect(Collectors.toList())); - } - - /** - * @since v9.0.0 - */ - @SafeVarargs - public static > AggregateReflectiveHandle anyOf(Callable... handles) { - return new AggregateReflectiveHandle<>(Arrays.asList(handles)); - } - - /** - * Relativize the stacktrace of exceptions that are thrown from the same location. - * The suppressed exception's ({@link Throwable#getSuppressed()}) stacktrace are relativized against - * the given exceptions stacktrace. - *

- * This is mostly useful when you have a trial-and-error mechanism that accumulates all the errors - * to throw them in case all the attempts have failed. This removes unnecessary line information to - * help the developer focus on important, non-repeated lines. - * - * @param ex the exception to have it's suppressed exceptions relativized. - * @return the same exception. - * @param the type of the exception. - */ - @ApiStatus.Experimental - public static T relativizeSuppressedExceptions(T ex) { - Objects.requireNonNull(ex, "Cannot relativize null exception"); - final StackTraceElement[] EMPTY_STACK_TRACE_ARRAY = new StackTraceElement[0]; - StackTraceElement[] mainStackTrace = ex.getStackTrace(); - - for (Throwable suppressed : ex.getSuppressed()) { - StackTraceElement[] suppressedStackTrace = suppressed.getStackTrace(); - List relativized = new ArrayList<>(10); - - for (int i = 0; i < suppressedStackTrace.length; i++) { - if (mainStackTrace.length <= i) { - relativized = null; - break; - } - - StackTraceElement mainTrace = mainStackTrace[i]; - StackTraceElement suppTrace = suppressedStackTrace[i]; - if (mainTrace.equals(suppTrace)) { - break; - } else { - relativized.add(suppTrace); - } - } - - if (relativized != null) { - // We might not know the line so let's not add this: - // if (!relativized.isEmpty()) relativized.remove(relativized.size() - 1); - suppressed.setStackTrace(relativized.toArray(EMPTY_STACK_TRACE_ARRAY)); - } - } - return ex; - } - - @SuppressWarnings("unchecked") - private static void throwException(Throwable exception) throws T { - throw (T) exception; - } - - /** - * Throws a checked exception (see {@link Exception}) silently without forcing the programmer to handle it. This is usually considered - * a very bad practice, as those errors are meant to be handled, so please use sparingly. You should just - * create a {@link RuntimeException} instead and putting the checked exception as a cause if necessary. - *

Usage

- *
{@code
-     *     void doStuff() throws IOException {}
-     *
-     *     void rethrowAsRuntime() {
-     *         try {
-     *             doStuff();
-     *         } catch (IOException ex) {
-     *             throw new RuntimeException(ex);
-     *         }
-     *     }
-     *
-     *     void ignoreTheLawsOfJavaQuantumMechanics() {
-     *         try {
-     *             doStuff();
-     *         } catch (IOException ex) {
-     *             throw XReflection.throwCheckedException(ex);
-     *         }
-     *     }
-     * }
- * @return {@code null}, but it's intended to be thrown, this is a hacky trick to stop the IDE - * from complaining about non-terminating statements. - */ - public static RuntimeException throwCheckedException(Throwable exception) { - Objects.requireNonNull(exception, "Cannot throw null exception"); - // The following commented statement is not needed because the exception was created somewhere else and the stacktrace reflects that. - // exception.setStackTrace(Arrays.stream(exception.getStackTrace()).skip(1).toArray(StackTraceElement[]::new)); - throwException(exception); - return null; // Trick the compiler to stfu for "throw" terminating statements. - } - - /** - * Adds the stacktrace of the current thread in case an error occurs in the given Future. - */ - @ApiStatus.Experimental - public static CompletableFuture stacktrace(@Nonnull CompletableFuture completableFuture) { - StackTraceElement[] currentStacktrace = Thread.currentThread().getStackTrace(); - return completableFuture.whenComplete((value, ex) -> { // Gets called even when it's completed. - if (ex == null) { - // This happens if for example someone does: - // completableFuture.exceptionally(e -> { e.printStackTrace(); return null; }) - completableFuture.complete(value); - return; - } - - try { - // Remove these: - // at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315) - // at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320) - // at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1770) - // at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) - // at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) - // at java.base/java.lang.Thread.run(Thread.java:1583) - StackTraceElement[] exStacktrace = ex.getStackTrace(); - if (exStacktrace.length >= 3) { - List clearStacktrace = new ArrayList<>(Arrays.asList(exStacktrace)); - Collections.reverse(clearStacktrace); - - Iterator iter = clearStacktrace.iterator(); - List watchClassNames = Arrays.asList("java.util.concurrent.CompletableFuture", - "java.util.concurrent.ThreadPoolExecutor", "java.util.concurrent.ForkJoinTask", - "java.util.concurrent.ForkJoinWorkerThread", "java.util.concurrent.ForkJoinPool"); - List watchMethodNames = Arrays.asList("postComplete", "encodeThrowable", "completeThrowable", - "tryFire", "run", "runWorker", "scan", "exec", "doExec", "topLevelExec", "uniWhenComplete"); - while (iter.hasNext()) { - StackTraceElement stackTraceElement = iter.next(); - String className = stackTraceElement.getClassName(); - String methodName = stackTraceElement.getMethodName(); - - // Let's keep this just as an indicator. - if (className.equals(Thread.class.getName())) continue; - - if (watchClassNames.stream().anyMatch(className::startsWith) && - watchMethodNames.stream().anyMatch(methodName::equals)) { - iter.remove(); - } else { - break; - } - } - - Collections.reverse(clearStacktrace); - exStacktrace = clearStacktrace.toArray(new StackTraceElement[0]); - } - - // Skip 2 -> the getStackTrace() method + this method - StackTraceElement[] finalCurrentStackTrace = Arrays.stream(currentStacktrace).skip(2).toArray(StackTraceElement[]::new); - ex.setStackTrace(concatenate(exStacktrace, finalCurrentStackTrace)); - } catch (Throwable ex2) { - ex.addSuppressed(ex2); - } finally { - completableFuture.completeExceptionally(ex); - } - }); - } - - @ApiStatus.Internal - public static T[] concatenate(T[] a, T[] b) { - int aLen = a.length; - int bLen = b.length; - - @SuppressWarnings("unchecked") - T[] c = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), aLen + bLen); - System.arraycopy(a, 0, c, 0, aLen); - System.arraycopy(b, 0, c, aLen, bLen); - - return c; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/ReflectiveConstraint.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/ReflectiveConstraint.java deleted file mode 100644 index 8d05adb78..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/ReflectiveConstraint.java +++ /dev/null @@ -1,13 +0,0 @@ -package taboolib.library.xseries.reflection.constraint; - -import taboolib.library.xseries.reflection.ReflectiveHandle; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -public interface ReflectiveConstraint { - String category(); - - String name(); - - boolean appliesTo(ReflectiveHandle handle); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/VisibilityConstraint.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/VisibilityConstraint.java deleted file mode 100644 index 0203bb314..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/constraint/VisibilityConstraint.java +++ /dev/null @@ -1,19 +0,0 @@ -package taboolib.library.xseries.reflection.constraint; - -import taboolib.library.xseries.reflection.ReflectiveHandle; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -public enum VisibilityConstraint implements ReflectiveConstraint { - PUBLIC { - @Override - public boolean appliesTo(ReflectiveHandle handle) { - return false; - } - }; - - @Override - public String category() { - return "Visibility"; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/ConstructorMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/ConstructorMemberHandle.java deleted file mode 100644 index a92a4810c..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/ConstructorMemberHandle.java +++ /dev/null @@ -1,74 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.parser.ReflectionParser; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodType; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.stream.Collectors; - -/** - * A handle for using reflection for {@link Constructor}. - */ -public class ConstructorMemberHandle extends MemberHandle { - protected ClassHandle[] parameterTypes = new ClassHandle[0]; - - public ConstructorMemberHandle(ClassHandle clazz) { - super(clazz); - } - - public ConstructorMemberHandle parameters(Class... parameterTypes) { - this.parameterTypes = Arrays.stream(parameterTypes).map(XReflection::of).toArray(ClassHandle[]::new); - return this; - } - - public ConstructorMemberHandle parameters(ClassHandle... parameterTypes) { - this.parameterTypes = parameterTypes; - return this; - } - - @Override - public MethodHandle reflect() throws ReflectiveOperationException { - if (isFinal) throw new UnsupportedOperationException("Constructor cannot be final: " + this); - if (makeAccessible) { - return clazz.getNamespace().getLookup().unreflectConstructor(reflectJvm()); - } else { - Class[] parameterTypes = FlaggedNamedMemberHandle.getParameters(this, this.parameterTypes); - return clazz.getNamespace().getLookup().findConstructor(clazz.unreflect(), - MethodType.methodType(void.class, parameterTypes)); - } - } - - @Override - public ConstructorMemberHandle signature(String declaration) { - return new ReflectionParser(declaration).imports(clazz.getNamespace()).parseConstructor(this); - } - - @SuppressWarnings("unchecked") - @Override - public Constructor reflectJvm() throws ReflectiveOperationException { - Class[] parameterTypes = FlaggedNamedMemberHandle.getParameters(this, this.parameterTypes); - return handleAccessible(clazz.unreflect().getDeclaredConstructor(parameterTypes)); - } - - @Override - public ConstructorMemberHandle clone() { - ConstructorMemberHandle handle = new ConstructorMemberHandle(clazz); - handle.parameterTypes = this.parameterTypes; - handle.isFinal = this.isFinal; - handle.makeAccessible = this.makeAccessible; - return handle; - } - - @Override - public String toString() { - String str = this.getClass().getSimpleName() + '{'; - if (makeAccessible) str += "protected/private "; - str += clazz.toString() + ' '; - str += '(' + Arrays.stream(parameterTypes).map(ClassHandle::toString).collect(Collectors.joining(", ")) + ')'; - return str + '}'; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/EnumMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/EnumMemberHandle.java deleted file mode 100644 index 71ffee66b..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/EnumMemberHandle.java +++ /dev/null @@ -1,112 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import org.intellij.lang.annotations.Pattern; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.invoke.MethodHandle; -import java.lang.reflect.Field; - -/** - * A handle for using reflection for enums, which are technically a {@link Field}. - */ -public class EnumMemberHandle extends NamedMemberHandle { - public EnumMemberHandle(ClassHandle clazz) { - super(clazz); - } - - public EnumMemberHandle map(MinecraftMapping mapping, @Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String name) { - super.map(mapping, name); - return this; - } - - public EnumMemberHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String... names) { - super.named(names); - return this; - } - - /** - * Why would you even want to use this method for enums? - */ - @Override - public MemberHandle signature(String declaration) { - throw new UnsupportedOperationException(); - } - - /** - * Use {@link #getEnumConstant()} instead. - */ - @NotNull - @Override - public MethodHandle unreflect() { - return super.unreflect(); - } - - /** - * Use {@link #getEnumConstant()} instead. - */ - @Override - public @Nullable MethodHandle reflectOrNull() { - return super.reflectOrNull(); - } - - /** - * Use {@link #getEnumConstant()} instead. - * If you want to use this, you can do so with: - *
{@code
-     *     Object enumConstant = handle.reflect().invoke();
-     * }
- */ - @Override - public MethodHandle reflect() throws ReflectiveOperationException { - Field jvm = reflectJvm(); - return clazz.getNamespace().getLookup().unreflectGetter(jvm); - } - - @Nullable - public Object getEnumConstant() { - try { - return reflectJvm().get(null); - } catch (ReflectiveOperationException ex) { - throw XReflection.throwCheckedException(ex); - } - } - - @SuppressWarnings("unchecked") - @Override - public Field reflectJvm() throws ReflectiveOperationException { - if (names.isEmpty()) throw new IllegalStateException("No enum names specified"); - NoSuchFieldException errors = null; - Field field = null; - - Class clazz = this.clazz.reflect(); - for (String name : this.names) { - if (field != null) break; - try { - field = clazz.getDeclaredField(name); - if (!field.isEnumConstant()) { - throw new NoSuchFieldException("Field named '" + name + "' was found but it's not an enum constant " + this); - } - } catch (NoSuchFieldException ex) { - field = null; - if (errors == null) errors = new NoSuchFieldException("None of the enums were found for " + this); - errors.addSuppressed(ex); - } - } - - if (field == null) throw XReflection.relativizeSuppressedExceptions(errors); - return handleAccessible(field); - } - - /** - * Why would you even want to use this method for enums? - */ - @Override - public EnumMemberHandle clone() { - throw new UnsupportedOperationException(); - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FieldMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FieldMemberHandle.java deleted file mode 100644 index 3894bdce0..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FieldMemberHandle.java +++ /dev/null @@ -1,209 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import taboolib.library.xseries.reflection.parser.ReflectionParser; -import org.intellij.lang.annotations.Pattern; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Modifier; -import java.util.Objects; - -/** - * A handle for using reflection for {@link Field}. - */ -public class FieldMemberHandle extends FlaggedNamedMemberHandle { - public static final MethodHandle MODIFIERS_FIELD; - - public static final DynamicClassHandle VarHandle = XReflection.classHandle() - .inPackage("java.lang.invoke") - .named("VarHandle"); - - // public final native - // @MethodHandle.PolymorphicSignature - // @IntrinsicCandidate - // void set(Object... args); - public static final MethodHandle VAR_HANDLE_SET = VarHandle.method() - .named("set").returns(void.class).parameters(Object[].class).reflectOrNull(); - private static final Object MODIFIERS_VAR_HANDLE; - - static { - Object modVarHandle = null; - MethodHandle modifierFieldJvm = null; - - try { - modifierFieldJvm = XReflection.of(Field.class).field().setter() - .named("modifiers").returns(int.class).unreflect(); - } catch (Exception ignored) { - // Java 18+ - } - - try { - VarHandle.reflect(); - - // MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup()); - MethodHandle PRIVATE_LOOKUP_IN = XReflection.of(MethodHandles.class).method() - .named("privateLookupIn").returns(MethodHandles.Lookup.class).parameters(Class.class, MethodHandles.Lookup.class).reflect(); - // lookup.findVarHandle(Field.class, "modifiers", int.class); - MethodHandle FIND_VAR_HANDLE = VarHandle.method() - .named("findVarHandle").returns(MethodHandles.Lookup.class).parameters(Class.class, String.class, Class.class).reflect(); - - MethodHandles.Lookup lookup = (MethodHandles.Lookup) PRIVATE_LOOKUP_IN.invoke(Field.class, MethodHandles.lookup()); - FIND_VAR_HANDLE.invoke(lookup, Field.class, "modifiers", int.class); - } catch (Throwable ignored) { - } - - MODIFIERS_VAR_HANDLE = modVarHandle; - MODIFIERS_FIELD = modifierFieldJvm; - } - - protected Boolean getter; - - public FieldMemberHandle(ClassHandle clazz) { - super(clazz); - } - - public FieldMemberHandle getter() { - this.getter = true; - return this; - } - - public FieldMemberHandle asStatic() { - super.asStatic(); - return this; - } - - public FieldMemberHandle asFinal() { - this.isFinal = true; - return this; - } - - public FieldMemberHandle makeAccessible() { - super.makeAccessible(); - return this; - } - - public FieldMemberHandle setter() { - this.getter = false; - return this; - } - - @Override - public FieldMemberHandle returns(Class clazz) { - super.returns(clazz); - return this; - } - - @Override - public FieldMemberHandle returns(ClassHandle clazz) { - super.returns(clazz); - return this; - } - - @Override - public FieldMemberHandle clone() { - FieldMemberHandle handle = new FieldMemberHandle(clazz); - handle.returnType = this.returnType; - handle.getter = this.getter; - handle.isFinal = this.isFinal; - handle.makeAccessible = this.makeAccessible; - handle.names.addAll(this.names); - return handle; - } - - @Override - public MethodHandle reflect() throws ReflectiveOperationException { - Field jvm = reflectJvm(); - if (getter) { - return clazz.getNamespace().getLookup().unreflectGetter(jvm); - } else { - return clazz.getNamespace().getLookup().unreflectSetter(jvm); - } - } - - @Override - public FieldMemberHandle signature(String declaration) { - return new ReflectionParser(declaration).imports(clazz.getNamespace()).parseField(this); - } - - public FieldMemberHandle map(MinecraftMapping mapping, @Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String name) { - super.map(mapping, name); - return this; - } - - public FieldMemberHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String... names) { - super.named(names); - return this; - } - - @Override - protected T handleAccessible(T field) throws ReflectiveOperationException { - field = super.handleAccessible(field); - if (field == null) return null; - if (!getter && isFinal && isStatic) { - try { - int unfinalModifiers = field.getModifiers() & ~Modifier.FINAL; - if (MODIFIERS_VAR_HANDLE != null) { - VAR_HANDLE_SET.invoke(MODIFIERS_VAR_HANDLE, field, unfinalModifiers); - } else if (MODIFIERS_FIELD != null) { - MODIFIERS_FIELD.invoke(field, unfinalModifiers); - } else { - throw new IllegalAccessException("Current Java version doesn't support modifying final fields. " + this); - } - } catch (Throwable e) { - throw new ReflectiveOperationException("Cannot unfinal field " + this, e); - } - } - return field; - } - - @SuppressWarnings("unchecked") - @Override - public Field reflectJvm() throws ReflectiveOperationException { - Objects.requireNonNull(returnType, "Return type not specified"); - Objects.requireNonNull(getter, "Not specified whether the method is a getter or setter"); - if (names.isEmpty()) throw new IllegalStateException("No names specified"); - NoSuchFieldException errors = null; - Field field = null; - - Class clazz = this.clazz.reflect(); - Class returnType = getReturnType(); - - for (String name : this.names) { - if (field != null) break; - try { - field = clazz.getDeclaredField(name); - if (field.getType() != returnType) { - throw new NoSuchFieldException("Field named '" + name + "' was found but the types don't match: " + field + " != " + this); - } - if (isFinal && !Modifier.isFinal(field.getModifiers())) { - throw new NoSuchFieldException("Field named '" + name + "' was found but it's not final: " + field + " != " + this); - } - } catch (NoSuchFieldException ex) { - field = null; - if (errors == null) errors = new NoSuchFieldException("None of the fields were found for " + this); - errors.addSuppressed(ex); - } - } - - if (field == null) throw XReflection.relativizeSuppressedExceptions(errors); - return handleAccessible(field); - } - - @Override - public String toString() { - String str = this.getClass().getSimpleName() + '{'; - if (makeAccessible) str += "protected/private "; - if (isFinal) str += "final "; - if (returnType != null) str += returnType + " "; - str += String.join("/", names); - return str + '}'; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FlaggedNamedMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FlaggedNamedMemberHandle.java deleted file mode 100644 index 0a8c38c3f..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/FlaggedNamedMemberHandle.java +++ /dev/null @@ -1,59 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; - -/** - * This class should not be used directly. - *

- * Any reflective JVM object that has a name and can have modifiers (public, private, final static, etc), - * like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} - */ -public abstract class FlaggedNamedMemberHandle extends NamedMemberHandle { - protected ClassHandle returnType; - protected boolean isStatic; - - protected FlaggedNamedMemberHandle(ClassHandle clazz) { - super(clazz); - } - - public FlaggedNamedMemberHandle asStatic() { - this.isStatic = true; - return this; - } - - public FlaggedNamedMemberHandle returns(Class clazz) { - this.returnType = XReflection.of(clazz); - return this; - } - - public FlaggedNamedMemberHandle returns(ClassHandle clazz) { - this.returnType = clazz; - return this; - } - - public static Class[] getParameters(Object owner, ClassHandle[] parameterTypes) { - Class[] classes = new Class[parameterTypes.length]; - int i = 0; - for (ClassHandle parameterType : parameterTypes) { - try { - classes[i++] = parameterType.unreflect(); - } catch (Throwable ex) { - throw XReflection.throwCheckedException(new ReflectiveOperationException( - "Unknown parameter " + parameterType + " for " + owner, ex - )); - } - } - return classes; - } - - protected Class getReturnType() { - try { - return this.returnType.unreflect(); - } catch (Throwable ex) { - throw XReflection.throwCheckedException(new ReflectiveOperationException( - "Unknown return type " + returnType + " for " + this - )); - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MemberHandle.java deleted file mode 100644 index c559cc27e..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MemberHandle.java +++ /dev/null @@ -1,66 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.ReflectiveHandle; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import org.intellij.lang.annotations.Language; - -import java.lang.invoke.MethodHandle; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Member; -import java.lang.reflect.Modifier; - -/** - * This class should not be used directly. - *

- * Any object that is a member of a {@link Class}. - */ -public abstract class MemberHandle implements ReflectiveHandle { - protected boolean makeAccessible, isFinal; - protected final ClassHandle clazz; - - protected MemberHandle(ClassHandle clazz) {this.clazz = clazz;} - - /** - * Returns the class associated with this member. - * This is not meant to be used directly. - */ - public ClassHandle getClassHandle() { - return clazz; - } - - /** - * If this member is known to be private or a final field. - */ - public MemberHandle makeAccessible() { - this.makeAccessible = true; - return this; - } - - /** - * Changes the signature of this handle according to the given java code (see {@link taboolib.library.xseries.reflection.parser.ReflectionParser}) - * This overrides the current declaration, but names will be kept and any new names specified in the signature will also be added. - */ - public abstract MemberHandle signature(@Language("Java") String declaration); - - public abstract MethodHandle reflect() throws ReflectiveOperationException; - - /** - * It's preferred to use one of the {@link MethodHandle} methods instead. - * This method should only be used for special cases when a direct JVM object is needed. - * @see #reflect() - */ - public abstract T reflectJvm() throws ReflectiveOperationException; - - /** - * Handles private/final declarations. - */ - protected T handleAccessible(T accessibleObject) throws ReflectiveOperationException { - // Package-private classes or private inner classes. - if (this.makeAccessible || Modifier.isPrivate(accessibleObject.getDeclaringClass().getModifiers())) - accessibleObject.setAccessible(true); - return accessibleObject; - } - - @Override - public abstract MemberHandle clone(); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MethodMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MethodMemberHandle.java deleted file mode 100644 index 8c5c9655d..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/MethodMemberHandle.java +++ /dev/null @@ -1,126 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import taboolib.library.xseries.reflection.parser.ReflectionParser; -import org.intellij.lang.annotations.Pattern; - -import java.lang.invoke.MethodHandle; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * A handle for using reflection for {@link Method}. - */ -public class MethodMemberHandle extends FlaggedNamedMemberHandle { - protected ClassHandle[] parameterTypes = new ClassHandle[0]; - - public MethodMemberHandle(ClassHandle clazz) { - super(clazz); - } - - /** - * Overrides any previously set parameters. - */ - public MethodMemberHandle parameters(ClassHandle... parameterTypes) { - this.parameterTypes = parameterTypes; - return this; - } - - public MethodMemberHandle returns(Class clazz) { - super.returns(clazz); - return this; - } - - public MethodMemberHandle returns(ClassHandle clazz) { - super.returns(clazz); - return this; - } - - public MethodMemberHandle asStatic() { - super.asStatic(); - return this; - } - - public MethodMemberHandle parameters(Class... parameterTypes) { - this.parameterTypes = Arrays.stream(parameterTypes).map(XReflection::of).toArray(ClassHandle[]::new); - return this; - } - - @Override - public MethodHandle reflect() throws ReflectiveOperationException { - return clazz.getNamespace().getLookup().unreflect(reflectJvm()); - } - - @Override - public MethodMemberHandle signature(String declaration) { - return new ReflectionParser(declaration).imports(clazz.getNamespace()).parseMethod(this); - } - - public MethodMemberHandle map(MinecraftMapping mapping, @Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String name) { - super.map(mapping, name); - return this; - } - - public MethodMemberHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String... names) { - super.named(names); - return this; - } - - @SuppressWarnings("unchecked") - @Override - public Method reflectJvm() throws ReflectiveOperationException { - Objects.requireNonNull(returnType, "Return type not specified"); - if (names.isEmpty()) throw new IllegalStateException("No names specified"); - - NoSuchMethodException errors = null; - Method method = null; - - Class clazz = this.clazz.reflect(); - Class[] parameterTypes = FlaggedNamedMemberHandle.getParameters(this, this.parameterTypes); - Class returnType = getReturnType(); - - for (String name : this.names) { - if (method != null) break; - try { - method = clazz.getDeclaredMethod(name, parameterTypes); - if (method.getReturnType() != returnType) { - throw new NoSuchMethodException("Method named '" + name + "' was found but the return types don't match: " + this.returnType + " != " + method); - } - } catch (NoSuchMethodException ex) { - method = null; - if (errors == null) errors = new NoSuchMethodException("None of the methods were found for " + this); - errors.addSuppressed(ex); - } - } - - if (method == null) throw XReflection.relativizeSuppressedExceptions(errors); - return handleAccessible(method); - } - - @Override - public MethodMemberHandle clone() { - MethodMemberHandle handle = new MethodMemberHandle(clazz); - handle.returnType = this.returnType; - handle.parameterTypes = this.parameterTypes; - handle.isFinal = this.isFinal; - handle.makeAccessible = this.makeAccessible; - handle.names.addAll(this.names); - return handle; - } - - @Override - public String toString() { - String str = this.getClass().getSimpleName() + '{'; - if (makeAccessible) str += "protected/private "; - if (isFinal) str += "final "; - if (returnType != null) str += returnType + " "; - str += String.join("/", names); - str += '(' + Arrays.stream(parameterTypes).map(ClassHandle::toString).collect(Collectors.joining(", ")) + ')'; - return str + '}'; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedMemberHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedMemberHandle.java deleted file mode 100644 index c410929fb..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedMemberHandle.java +++ /dev/null @@ -1,44 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import taboolib.library.xseries.reflection.jvm.classes.ClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftMapping; -import org.intellij.lang.annotations.Pattern; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * This class should not be used directly. - *

- * Any reflective JVM object that has a name, like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} - * the other being {@link java.lang.reflect.Constructor} which doesn't have a name. - */ -public abstract class NamedMemberHandle extends MemberHandle implements NamedReflectiveHandle { - protected final Set names = new HashSet<>(5); - - @NotNull - @Override - public Set getPossibleNames() { - return names; - } - - protected NamedMemberHandle(ClassHandle clazz) { - super(clazz); - } - - public NamedMemberHandle map(MinecraftMapping mapping, @Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String name) { - this.names.add(name); - return this; - } - - public NamedMemberHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String... names) { - this.names.addAll(Arrays.asList(names)); - return this; - } - - @Override - public abstract NamedMemberHandle clone(); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedReflectiveHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedReflectiveHandle.java deleted file mode 100644 index bf41c9c66..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/NamedReflectiveHandle.java +++ /dev/null @@ -1,13 +0,0 @@ -package taboolib.library.xseries.reflection.jvm; - -import javax.annotation.Nonnull; -import java.util.Set; - -/** - * Any declaration in Java that can be named (all except constructors). - * This class should not be used directly. - */ -public interface NamedReflectiveHandle { - @Nonnull - Set getPossibleNames(); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/ClassHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/ClassHandle.java deleted file mode 100644 index 21e541775..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/ClassHandle.java +++ /dev/null @@ -1,111 +0,0 @@ -package taboolib.library.xseries.reflection.jvm.classes; - -import taboolib.library.xseries.reflection.ReflectiveHandle; -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import taboolib.library.xseries.reflection.jvm.*; -import taboolib.library.xseries.reflection.parser.ReflectionParser; -import org.intellij.lang.annotations.Language; - -import java.util.Objects; - -/** - * @see DynamicClassHandle - * @see StaticClassHandle - */ -public abstract class ClassHandle implements ReflectiveHandle>, NamedReflectiveHandle { - protected final ReflectiveNamespace namespace; - - protected ClassHandle(ReflectiveNamespace namespace) { - this.namespace = namespace; - namespace.link(this); - } - - public abstract ClassHandle asArray(int dimensions); - - public final ClassHandle asArray() { - return asArray(1); - } - - public abstract boolean isArray(); - - public DynamicClassHandle inner(@Language("Java") String declaration) { - return inner(namespace.classHandle(declaration)); - } - - /** - * @param handle the handle to put the inner class information in. - * @return the same object as the one provided in the parameter. - * @param the type of the class handle. - */ - public T inner(T handle) { - Objects.requireNonNull(handle, "Inner handle is null"); - if (this == handle) throw new IllegalArgumentException("Same instance: " + this); - handle.parent = this; - namespace.link(this); - return handle; - } - - /** - * The array dimension of this class. - * @return -1 if this class cannot be found, 0 if not an array, otherwise a positive number. - */ - public int getDimensionCount() { - int count = -1; - Class clazz = reflectOrNull(); - if (clazz == null) return count; - - do { - clazz = clazz.getComponentType(); - count++; - } while (clazz != null); - - return count; - } - - public ReflectiveNamespace getNamespace() { - return namespace; - } - - public MethodMemberHandle method() { - return new MethodMemberHandle(this); - } - - public MethodMemberHandle method(@Language("Java") String declaration) { - return createParser(declaration).parseMethod(method()); - } - - public EnumMemberHandle enums() { - return new EnumMemberHandle(this); - } - - public FieldMemberHandle field() { - return new FieldMemberHandle(this); - } - - public FieldMemberHandle field(@Language("Java") String declaration) { - return createParser(declaration).parseField(field()); - } - - public ConstructorMemberHandle constructor(@Language("Java") String declaration) { - return createParser(declaration).parseConstructor(constructor()); - } - - public ConstructorMemberHandle constructor() { - return new ConstructorMemberHandle(this); - } - - public ConstructorMemberHandle constructor(Class... parameters) { - return constructor().parameters(parameters); - } - - public ConstructorMemberHandle constructor(ClassHandle... parameters) { - return constructor().parameters(parameters); - } - - private ReflectionParser createParser(@Language("Java") String declaration) { - return new ReflectionParser(declaration).imports(this.namespace); - } - - @Override - public abstract ClassHandle clone(); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/DynamicClassHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/DynamicClassHandle.java deleted file mode 100644 index d3ab125be..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/DynamicClassHandle.java +++ /dev/null @@ -1,130 +0,0 @@ -package taboolib.library.xseries.reflection.jvm.classes; - -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import taboolib.library.xseries.reflection.XReflection; -import com.google.common.base.Strings; -import org.intellij.lang.annotations.Pattern; - -import javax.annotation.Nonnull; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -/** - * @see StaticClassHandle - */ -public class DynamicClassHandle extends ClassHandle { - protected ClassHandle parent; - protected String packageName; - protected final Set classNames = new HashSet<>(5); - protected int array; - - public DynamicClassHandle(ReflectiveNamespace namespace) { - super(namespace); - } - - public DynamicClassHandle inPackage(@Pattern(PackageHandle.JAVA_PACKAGE_PATTERN) @Nonnull String packageName) { - Objects.requireNonNull(packageName, "Null package name"); - this.packageName = packageName; - return this; - } - - public DynamicClassHandle inPackage(@Nonnull PackageHandle packageHandle) { - // noinspection PatternValidation - return inPackage(packageHandle, ""); - } - - public DynamicClassHandle inPackage(@Nonnull PackageHandle packageHandle, - @Pattern(PackageHandle.JAVA_PACKAGE_PATTERN) @Nonnull String packageName) { - Objects.requireNonNull(packageHandle, "Null package handle type"); - Objects.requireNonNull(packageName, "Null package handle name"); - if (parent != null) - throw new IllegalStateException("Cannot change package of an inner class: " + packageHandle + " -> " + packageName); - this.packageName = packageHandle.getPackage(packageName); - return this; - } - - public DynamicClassHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) @Nonnull String... classNames) { - Objects.requireNonNull(classNames); - for (String className : this.classNames) { - Objects.requireNonNull(className, () -> "Cannot add null class name from: " + Arrays.toString(classNames) + " to " + this); - } - this.classNames.addAll(Arrays.asList(classNames)); - return this; - } - - public String[] reflectClassNames() { - if (this.parent == null) Objects.requireNonNull(packageName, "Package name is null"); - String[] classNames = new String[this.classNames.size()]; - Class parent = this.parent == null ? null : XReflection.of(this.parent.unreflect()).asArray(0).unreflect(); - - int i = 0; - for (String className : this.classNames) { - @SuppressWarnings("NonConstantStringShouldBeStringBuffer") - String clazz; - if (parent == null) clazz = packageName + '.' + className; - else clazz = parent.getName() + '$' + className; - - if (array != 0) clazz = Strings.repeat("[", array) + 'L' + clazz + ';'; - classNames[i++] = clazz; - } - - return classNames; - } - - @Override - public DynamicClassHandle clone() { - DynamicClassHandle handle = new DynamicClassHandle(namespace); - handle.array = this.array; - handle.parent = this.parent; - handle.packageName = this.packageName; - handle.classNames.addAll(this.classNames); - return handle; - } - - @Override - public Class reflect() throws ClassNotFoundException { - String[] classNames = reflectClassNames(); - if (classNames.length == 0) throw new IllegalStateException("No class name specified for " + this); - - ClassNotFoundException errors = null; - for (String className : classNames) { - try { - return Class.forName(className); - } catch (ClassNotFoundException ex) { - if (errors == null) errors = new ClassNotFoundException("None of the classes were found"); - errors.addSuppressed(ex); - } - } - - throw XReflection.relativizeSuppressedExceptions(errors); - } - - @Override - public DynamicClassHandle asArray(int dimension) { - if (dimension < 0) throw new IllegalArgumentException("Array dimension cannot be negative: " + dimension); - this.array = dimension; - return this; - } - - @Override - public boolean isArray() { - return this.array > 0; - } - - @Override - public Set getPossibleNames() { - return classNames; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + '{' + - (parent == null ? "" : parent + " -> ") + - (parent == null ? packageName : (packageName == null ? "" : packageName)) + - '(' + String.join("|", classNames) + ')' + - (array == 0 ? "" : "[" + array + ']') + - " }"; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/PackageHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/PackageHandle.java deleted file mode 100644 index 981a12c6c..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/PackageHandle.java +++ /dev/null @@ -1,28 +0,0 @@ -package taboolib.library.xseries.reflection.jvm.classes; - -import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; - -/** - * A handle that can provide base package names or translate names based on current mappings. - */ -public interface PackageHandle { - @Language("RegExp") - @ApiStatus.Internal - String JAVA_PACKAGE_PATTERN = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - - @Language("RegExp") - @ApiStatus.Internal - String JAVA_IDENTIFIER_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - - @Nonnull - String packageId(); - - @Nonnull - String getBasePackageName(); - - @Nonnull - String getPackage(@Nonnull String packageName); -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/StaticClassHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/StaticClassHandle.java deleted file mode 100644 index 004b65724..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/jvm/classes/StaticClassHandle.java +++ /dev/null @@ -1,74 +0,0 @@ -package taboolib.library.xseries.reflection.jvm.classes; - -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Array; -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -/** - * A class that is known at compile time and can be referenced directly. - *

- * The purpose of this handle is to use reflection on the members - * (fields, methods and constructors) of the corresponding class. - * @see DynamicClassHandle - */ -public class StaticClassHandle extends ClassHandle { - @NotNull - protected Class clazz; - - public StaticClassHandle(ReflectiveNamespace namespace, @NotNull Class clazz) { - super(namespace); - this.clazz = Objects.requireNonNull(clazz); - } - - private Class purifyClass() { - Class pureClazz = clazz; - - while (true) { - Class component = pureClazz.getComponentType(); - if (component != null) pureClazz = component; - else break; - } - - return Objects.requireNonNull(pureClazz); - } - - public StaticClassHandle asArray(int dimension) { - Class arrayClass = purifyClass(); - if (dimension > 0) { - for (int i = 0; i < dimension; i++) { - arrayClass = Array.newInstance(arrayClass, 0).getClass(); - } - } - this.clazz = arrayClass; - return this; - } - - @Override - public Class reflect() throws ClassNotFoundException { - return this.clazz; - } - - @Override - public boolean isArray() { - return clazz.isArray(); - } - - @Override - public Set getPossibleNames() { - return Collections.singleton(clazz.getSimpleName()); - } - - @Override - public StaticClassHandle clone() { - return new StaticClassHandle(namespace, this.clazz); - } - - @Override - public String toString() { - return "StaticClassHandle(" + clazz + ')'; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftClassHandle.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftClassHandle.java deleted file mode 100644 index 8c0445d92..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftClassHandle.java +++ /dev/null @@ -1,52 +0,0 @@ -package taboolib.library.xseries.reflection.minecraft; - -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import org.intellij.lang.annotations.Language; -import org.intellij.lang.annotations.Pattern; - -/** - * A specialized type of {@link DynamicClassHandle} which currently doesn't - * have any extra functionalities. - */ -public class MinecraftClassHandle extends DynamicClassHandle { - public MinecraftClassHandle(ReflectiveNamespace namespace) { - super(namespace); - } - - public MinecraftClassHandle inPackage(MinecraftPackage minecraftPackage) { - super.inPackage(minecraftPackage); - return this; - } - - public MinecraftClassHandle inPackage(MinecraftPackage minecraftPackage, @Pattern(PackageHandle.JAVA_PACKAGE_PATTERN) String packageName) { - super.inPackage(minecraftPackage, packageName); - return this; - } - - @Override - public MinecraftClassHandle inner(@Language("Java") String declaration) { - return inner(namespace.ofMinecraft(declaration)); - } - - public MinecraftClassHandle named(@Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String... clazzNames) { - super.named(clazzNames); - return this; - } - - public MinecraftClassHandle map(MinecraftMapping mapping, @Pattern(PackageHandle.JAVA_IDENTIFIER_PATTERN) String className) { - this.classNames.add(className); - return this; - } - - @Override - public MinecraftClassHandle clone() { - MinecraftClassHandle handle = new MinecraftClassHandle(namespace); - handle.array = this.array; - handle.parent = this.parent; - handle.packageName = this.packageName; - handle.classNames.addAll(this.classNames); - return handle; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftConnection.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftConnection.java deleted file mode 100644 index d121ba784..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftConnection.java +++ /dev/null @@ -1,124 +0,0 @@ -package taboolib.library.xseries.reflection.minecraft; - -import taboolib.library.xseries.reflection.XReflection; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.invoke.MethodHandle; -import java.util.Arrays; -import java.util.Objects; - -import static taboolib.library.xseries.reflection.XReflection.ofMinecraft; -import static taboolib.library.xseries.reflection.XReflection.v; - -/** - * Provides general packet-related API for players. - *

- * Clientbound Packets are considered fake - * updates to the client without changing the actual data. Since all the data is handled - * by the server. - */ -public final class MinecraftConnection { - public static final MinecraftClassHandle ServerPlayer = ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.level") - .map(MinecraftMapping.MOJANG, "ServerPlayer") - .map(MinecraftMapping.SPIGOT, "EntityPlayer"); - public static final MinecraftClassHandle CraftPlayer = ofMinecraft() - .inPackage(MinecraftPackage.CB, "entity") - .named("CraftPlayer"); - public static final MinecraftClassHandle ServerPlayerConnection = ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.network") - .map(MinecraftMapping.MOJANG, "ServerPlayerConnection") - .map(MinecraftMapping.SPIGOT, "PlayerConnection"); - public static final MinecraftClassHandle ServerGamePacketListenerImpl = ofMinecraft() - .inPackage(MinecraftPackage.NMS, "server.network") - .map(MinecraftMapping.MOJANG, "ServerGamePacketListenerImpl") - .map(MinecraftMapping.SPIGOT, "PlayerConnection"); - public static final MinecraftClassHandle Packet = ofMinecraft() - .inPackage(MinecraftPackage.NMS, "network.protocol") - .map(MinecraftMapping.SPIGOT, "Packet"); - - private static final MethodHandle PLAYER_CONNECTION = ServerPlayer - // .getterField(v(20, 5, "connection").v(20, "c").v(17, "b").orElse("playerConnection")) - .field().getter() - .returns(ServerGamePacketListenerImpl) - .map(MinecraftMapping.MOJANG, "connection") - .map(MinecraftMapping.OBFUSCATED, v(20, "c").v(17, "b").orElse("playerConnection")) - .unreflect(); - /** - * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. - * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. - * Used mainly for handling packet related operations. - *

- * This is also where the famous player {@code ping} field comes from! - */ - private static final MethodHandle GET_HANDLE = CraftPlayer - .method() - .named("getHandle") - .returns(ServerPlayer) - .unreflect(); - /** - * Sends a packet to the player's client through a {@code NetworkManager} which - * is where {@code ProtocolLib} controls packets by injecting channels! - */ - private static final MethodHandle SEND_PACKET = ServerPlayerConnection - .method() - .returns(void.class) - .parameters(Packet) - .map(MinecraftMapping.MOJANG, "send") - .map(MinecraftMapping.OBFUSCATED, v(20, 2, "b").v(18, "a").orElse("sendPacket")) - .unreflect(); - - @NotNull - public static Object getHandle(@NotNull Player player) { - Objects.requireNonNull(player, "Cannot get handle of null player"); - try { - return GET_HANDLE.invoke(player); - } catch (Throwable throwable) { - throw XReflection.throwCheckedException(throwable); - } - } - - /** - * @return null if the player is no longer online. - */ - @Nullable - public static Object getConnection(@NotNull Player player) { - Objects.requireNonNull(player, "Cannot get connection of null player"); - try { - Object handle = GET_HANDLE.invoke(player); - return PLAYER_CONNECTION.invoke(handle); - } catch (Throwable throwable) { - throw XReflection.throwCheckedException(throwable); - } - } - - /** - * Sends a packet to the player asynchronously if they're online. - * Packets are thread-safe. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @since 1.0.0 - */ - @NotNull - public static void sendPacket(@NotNull Player player, @NotNull Object... packets) { - Objects.requireNonNull(player, () -> "Can't send packet to null player: " + Arrays.toString(packets)); - Objects.requireNonNull(packets, () -> "Can't send null packets to player: " + player); - try { - Object handle = GET_HANDLE.invoke(player); - Object connection = PLAYER_CONNECTION.invoke(handle); - - // Checking if the connection is not null is enough. There is no need to check if the player is online. - if (connection != null) { - for (Object packet : packets) { - Objects.requireNonNull(packet, "Null packet detected between packets array"); - SEND_PACKET.invoke(connection, packet); - } - } - } catch (Throwable throwable) { - throw new RuntimeException("Failed to send packet to " + player + ": " + Arrays.toString(packets), throwable); - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftMapping.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftMapping.java deleted file mode 100644 index 225904d4b..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -package taboolib.library.xseries.reflection.minecraft; - -/** - * Mostly for code documentation purposes. - * Paper started using Mojang-mapped names since 1.20.5 - *

- * A useful up-to-date resource used to compare mappings is Cephx. - */ -public enum MinecraftMapping { - MOJANG, OBFUSCATED, SPIGOT; -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftPackage.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftPackage.java deleted file mode 100644 index 5126d4845..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/MinecraftPackage.java +++ /dev/null @@ -1,37 +0,0 @@ -package taboolib.library.xseries.reflection.minecraft; - -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import org.intellij.lang.annotations.Pattern; - -/** - * Common Minecraft packages. - */ -public enum MinecraftPackage implements PackageHandle { - NMS(XReflection.NMS_PACKAGE), CB(XReflection.CRAFTBUKKIT_PACKAGE), - BUKKIT("org.bukkit"), SPIGOT("org.spigotmc"); - - private final String packageId; - - MinecraftPackage(String packageName) {this.packageId = packageName;} - - @Override - public String packageId() { - return name(); - } - - @Override - public String getBasePackageName() { - return packageId; - } - - @Override - public String getPackage(@Pattern(PackageHandle.JAVA_PACKAGE_PATTERN) String packageName) { - if (packageName.startsWith(".") || packageName.endsWith(".")) - throw new IllegalArgumentException("Package name must not start or end with a dot: " + packageName + " (" + this + ')'); - if (!packageName.isEmpty() && (this != MinecraftPackage.NMS || XReflection.supports(17))) { - return packageId + '.' + packageName; - } - return packageId; - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/NMSExtras.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/NMSExtras.java deleted file mode 100644 index 76b087152..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/minecraft/NMSExtras.java +++ /dev/null @@ -1,845 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2024 Crypto Morin - * - * 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 taboolib.library.xseries.reflection.minecraft; - -import taboolib.library.xseries.XSound; -import taboolib.library.xseries.reflection.XReflection; -import org.bukkit.Chunk; -import org.bukkit.DyeColor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; - -import javax.annotation.Nullable; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.util.*; - -import static taboolib.library.xseries.reflection.XReflection.*; - -/** - * Note: This class is currently really unorganized and unstable. It needs a proper recode. - *

- * A class that provides various different essential features that the API - * didn't/doesn't support. - *

- * All the parameters are non-null. - * - * @author Crypto Morin - * @version 5.5.0 - */ -@SuppressWarnings("unused") -public final class NMSExtras { - public static final Class EntityLiving = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .map(MinecraftMapping.MOJANG, "LivingEntity") - .map(MinecraftMapping.SPIGOT, "EntityLiving") - .unreflect(); - private static final MethodHandle GET_ENTITY_HANDLE; - public static final MethodHandle EXP_PACKET; - public static final MethodHandle ENTITY_PACKET; - public static final MethodHandle WORLD_HANDLE, ENTITY_HANDLE; - public static final MethodHandle LIGHTNING_ENTITY; - public static final MethodHandle VEC3D; - public static final MethodHandle GET_DATA_WATCHER, DATA_WATCHER_GET_ITEM, DATA_WATCHER_SET_ITEM; - public static final MethodHandle PACKET_PLAY_OUT_OPEN_SIGN_EDITOR, PACKET_PLAY_OUT_BLOCK_CHANGE; - - public static final MethodHandle ANIMATION_PACKET, ANIMATION_TYPE, ANIMATION_ENTITY_ID; - - public static final MethodHandle PLAY_OUT_MULTI_BLOCK_CHANGE_PACKET, MULTI_BLOCK_CHANGE_INFO, CHUNK_WRAPPER_SET, CHUNK_WRAPPER, SHORTS_OR_INFO, SET_BlockState; - - public static final MethodHandle BLOCK_POSITION; - public static final MethodHandle PLAY_BLOCK_ACTION; - public static final MethodHandle GET_BUKKIT_ENTITY; - public static final MethodHandle GET_BLOCK_TYPE; - public static final MethodHandle GET_BLOCK, GET_IBlockState, SANITIZE_LINES, TILE_ENTITY_SIGN, - TILE_ENTITY_SIGN__GET_UPDATE_PACKET, TILE_ENTITY_SIGN__SET_LINE, SIGN_TEXT; - - public static final Class BlockState = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level.block.state") - .map(MinecraftMapping.MOJANG, "BlockState") - .map(MinecraftMapping.SPIGOT, "IBlockData") - .unreflect(); - public static final Class MULTI_BLOCK_CHANGE_INFO_CLASS = null; // getNMSClass("PacketPlayOutMultiBlockChange$MultiBlockChangeInfo") - - static { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - - MethodHandle expPacket = null; - MethodHandle entityPacket = null; - MethodHandle worldHandle = null, entityHandle = null; - MethodHandle lightning = null; - MethodHandle vec3D = null; - MethodHandle signEditorPacket = null, packetPlayOutBlockChange = null; - - MethodHandle animationPacket = null; - MethodHandle animationType = null; - MethodHandle animationEntityId = null; - - MethodHandle getBukkitEntity = null; - MethodHandle blockPosition = null; - MethodHandle playBlockAction = null; - MethodHandle getBlockType = null; - MethodHandle getBlock = null; - MethodHandle getIBlockData = null; - MethodHandle sanitizeLines = null; - MethodHandle tileEntitySign = null, tileEntitySign_getUpdatePacket = null, tileEntitySign_setLine = null, signText = null; - - MethodHandle playOutMultiBlockChange = null, multiBlockChangeInfo = null, chunkWrapper = null, chunkWrapperSet = null, - shortsOrInfo = null, setBlockData = null, getDataWatcher = null, dataWatcherGetItem = null, - dataWatcherSetItem = null, getHandle = null; - - try { - Class CraftEntityClass = ofMinecraft().inPackage(MinecraftPackage.CB, "entity") - .named("CraftEntity").unreflect(); - Class nmsEntityType = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .map(MinecraftMapping.MOJANG, "EntityType") - .map(MinecraftMapping.SPIGOT, "EntityTypes").unreflect(); - Class nmsEntity = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .named("Entity").unreflect(); - Class craftEntity = ofMinecraft().inPackage(MinecraftPackage.CB, "entity") - .named("CraftEntity").unreflect(); - Class nmsVec3D = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.phys") - .map(MinecraftMapping.MOJANG, "Vec3") - .map(MinecraftMapping.SPIGOT, "Vec3D").unreflect(); - Class world = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level") - .map(MinecraftMapping.MOJANG, "Level") - .map(MinecraftMapping.SPIGOT, "World").unreflect(); - Class signOpenPacket = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundOpenSignEditorPacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutOpenSignEditor").unreflect(); - Class packetPlayOutBlockChangeClass = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundBlockUpdatePacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutBlockChange").unreflect(); - Class CraftMagicNumbers = ofMinecraft().inPackage(MinecraftPackage.CB, "util") - .named("CraftMagicNumbers").unreflect(); - Class CraftSign = ofMinecraft().inPackage(MinecraftPackage.CB, "block") - .named("CraftSign").unreflect(); - Class IChatBaseComponent = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.chat") - .map(MinecraftMapping.MOJANG, "Component") - .map(MinecraftMapping.SPIGOT, "IChatBaseComponent").unreflect(); - Class TileEntitySign = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level.block.entity") - .map(MinecraftMapping.MOJANG, "SignBlockEntity") - .map(MinecraftMapping.SPIGOT, "TileEntitySign").unreflect(); - Class PacketPlayOutTileEntityData = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundBlockEntityDataPacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutTileEntityData").unreflect(); - Class DataWatcherClass = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.syncher") - .map(MinecraftMapping.MOJANG, "SynchedEntityData") - .map(MinecraftMapping.SPIGOT, "DataWatcher").unreflect(); - Class DataWatcherItemClass = - ofMinecraft().inPackage(MinecraftPackage.NMS, "network.syncher") - .map(MinecraftMapping.MOJANG, "SynchedEntityData$DataItem") - .map(MinecraftMapping.SPIGOT, "DataWatcher$Item").unreflect(); - - Class DataWatcherObject = XReflection.ofMinecraft().inPackage(MinecraftPackage.NMS, "network.syncher") - .map(MinecraftMapping.MOJANG, "EntityDataAccessor") - .map(MinecraftMapping.SPIGOT, "DataWatcherObject") - .unreflect(); - - getHandle = lookup.findVirtual(CraftEntityClass, "getHandle", MethodType.methodType(nmsEntity)); - getDataWatcher = XReflection.of(nmsEntity) - .method().returns(DataWatcherClass) - .map(MinecraftMapping.MOJANG, "getEntityData") - .map(MinecraftMapping.SPIGOT, v(21, "ar") - .v(20, 5, "ap") - .v(20, 4, "an") - .v(20, 2, "al") - .v(19, "aj") - .v(18, "ai") - .orElse("getDataWatcher") - ).unreflect(); - - - // public T get(DataWatcherObject datawatcherobject) { - // return this.b(datawatcherobject).b(); - // } - dataWatcherGetItem = XReflection.of(DataWatcherClass).method() - .returns(Object.class).parameters(DataWatcherObject) - .map(MinecraftMapping.MOJANG, "get") - .map(MinecraftMapping.SPIGOT, v(20, 5, "a").v(20, "b").v(18, "a").orElse("get")) - .unreflect(); - - /* - public void b(DataWatcherObject datawatcherobject, T t0) { - this.a(datawatcherobject, t0, false); - } - */ - dataWatcherSetItem = XReflection.of(DataWatcherClass).method() - .returns(void.class).parameters(DataWatcherObject, Object.class) - .map(MinecraftMapping.MOJANG, "set") - .map(MinecraftMapping.SPIGOT, v(20, 5, "a").v(18, "b").orElse("set")) - .unreflect(); - - getBukkitEntity = lookup.findVirtual(nmsEntity, "getBukkitEntity", MethodType.methodType(craftEntity)); - entityHandle = lookup.findVirtual(craftEntity, "getHandle", MethodType.methodType(nmsEntity)); - - // https://wiki.vg/Protocol#Set_Experience - // exp - lvl - total exp - expPacket = lookup.findConstructor(ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundSetExperiencePacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutExperience") - .unreflect(), MethodType.methodType( - void.class, float.class, int.class, int.class)); - // Lightning - if (!supports(16)) { - entityPacket = ofMinecraft().inPackage(MinecraftPackage.NMS) - .named("PacketPlayOutSpawnEntityWeather") - .constructor().parameters(nmsEntity).unreflect(); - } else { - vec3D = lookup.findConstructor(nmsVec3D, MethodType.methodType(void.class, - double.class, double.class, double.class)); - - List> spawnTypes = new ArrayList<>(Arrays.asList( - int.class, UUID.class, - double.class, double.class, double.class, float.class, float.class, - nmsEntityType, int.class, nmsVec3D) - ); - if (XReflection.supports(19)) spawnTypes.add(double.class); - entityPacket = lookup.findConstructor(ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundAddEntityPacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutSpawnEntity") - .unreflect(), - MethodType.methodType(void.class, spawnTypes)); - } - - worldHandle = lookup.findVirtual(ofMinecraft().inPackage(MinecraftPackage.CB).named("CraftWorld").unreflect(), "getHandle", MethodType.methodType( - ofMinecraft().inPackage(MinecraftPackage.NMS, "server.level") - .map(MinecraftMapping.MOJANG, "ServerLevel") - .map(MinecraftMapping.SPIGOT, "WorldServer").unreflect())); - - MinecraftClassHandle entityLightning = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .map(MinecraftMapping.MOJANG, "LightningBolt") - .map(MinecraftMapping.SPIGOT, "EntityLightning"); - if (!supports(16)) { - lightning = lookup.findConstructor(entityLightning.unreflect(), MethodType.methodType(void.class, - // world, x, y, z, isEffect, isSilent - world, double.class, double.class, double.class, boolean.class, boolean.class)); - } else { - lightning = lookup.findConstructor(entityLightning.unreflect(), MethodType.methodType(void.class, - // entitytype, world - nmsEntityType, world)); - } - - // Multi Block Change - Class playOutMultiBlockChangeClass = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundSectionBlocksUpdatePacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutMultiBlockChange") - .unreflect(); - Class chunkCoordIntPairClass = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level") - .map(MinecraftMapping.MOJANG, "ChunkPos") - .map(MinecraftMapping.SPIGOT, "ChunkCoordIntPair") - .unreflect(); - try { -// playOutMultiBlockChange = lookup.findConstructor(playOutMultiBlockChangeClass, MethodType.methodType(void.class)); -// multiBlockChangeInfo = lookup.findConstructor(MULTI_BLOCK_CHANGE_INFO_CLASS, MethodType.methodType(void.class, short.class, BlockState)); - - // a - chunk -// Field sectionPositionField = playOutMultiBlockChangeClass.getDeclaredField("a"); -// sectionPositionField.setAccessible(true); -// chunkWrapperSet = lookup.unreflectSetter(sectionPositionField); - - // b - shorts -// Field shortsField = playOutMultiBlockChangeClass.getDeclaredField("b"); -// shortsField.setAccessible(true); -// shortsOrInfo = lookup.unreflectSetter(shortsField); - - // c - block data -// Field blockDataField = playOutMultiBlockChangeClass.getDeclaredField("c"); -// blockDataField.setAccessible(true); -// setBlockData = lookup.unreflectSetter(blockDataField); - - // noinspection StatementWithEmptyBody - if (supports(16)) { -// Class sectionPosClass = getNMSClass("SectionPosition"); -// chunkWrapper = lookup.findConstructor(sectionPosClass, MethodType.methodType(int.class, int.class, int.class)); - } else - chunkWrapper = lookup.findConstructor(chunkCoordIntPairClass, MethodType.methodType(void.class, int.class, int.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - e.printStackTrace(); - } - - Class animation = ofMinecraft().inPackage(MinecraftPackage.NMS, "network.protocol.game") - .map(MinecraftMapping.MOJANG, "ClientboundAnimatePacket") - .map(MinecraftMapping.SPIGOT, "PacketPlayOutAnimation") - .unreflect(); - animationPacket = lookup.findConstructor(animation, - supports(17) ? MethodType.methodType(void.class, nmsEntity, int.class) : MethodType.methodType(void.class)); - - if (!supports(17)) { - Field field = animation.getDeclaredField("a"); - field.setAccessible(true); - animationEntityId = lookup.unreflectSetter(field); - field = animation.getDeclaredField("b"); - field.setAccessible(true); - animationType = lookup.unreflectSetter(field); - } - - - Class blockPos = ofMinecraft().inPackage(MinecraftPackage.NMS, "core") - .map(MinecraftMapping.MOJANG, "BlockPos") - .map(MinecraftMapping.SPIGOT, "BlockPosition") - .unreflect(); - Class block = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level.block") - .named("Block") - .unreflect(); - - blockPosition = lookup.findConstructor(blockPos, - v(19, MethodType.methodType(void.class, int.class, int.class, int.class)).orElse( - MethodType.methodType(void.class, double.class, double.class, double.class))); - - // public IBlockData getBlockState(BlockPosition blockposition) - getBlockType = XReflection.of(world).method().returns(BlockState).parameters(blockPos) - .map(MinecraftMapping.MOJANG, "getBlockState") - .map(MinecraftMapping.SPIGOT, v(18, "a_").orElse("getType")) - .unreflect(); - if (supports(21)) { - getBlock = XReflection.ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level.block.state") - .map(MinecraftMapping.MOJANG, "BlockBehaviour") - .map(MinecraftMapping.SPIGOT, "BlockBase") - .inner(XReflection.ofMinecraft() - .map(MinecraftMapping.MOJANG, "BlockStateBase") - .map(MinecraftMapping.SPIGOT, "BlockData")) - .method().returns(block) - .map(MinecraftMapping.MOJANG, "getBlock") - .map(MinecraftMapping.SPIGOT, "b") - .unreflect(); - } else { - getBlock = XReflection.of(BlockState).method().returns(block) - .map(MinecraftMapping.MOJANG, "getBlock") - .map(MinecraftMapping.SPIGOT, v(18, "b").orElse("getBlock")) - .unreflect(); - } - playBlockAction = XReflection.of(world).method().returns(void.class).parameters(blockPos, block, int.class, int.class) - .map(MinecraftMapping.MOJANG, "blockEvent") - .map(MinecraftMapping.SPIGOT, v(18, "a").orElse("playBlockAction")) - .unreflect(); - - signEditorPacket = lookup.findConstructor(signOpenPacket, - v(20, MethodType.methodType(void.class, blockPos, boolean.class)) - .orElse(MethodType.methodType(void.class, blockPos))); - if (supports(17)) { - packetPlayOutBlockChange = lookup.findConstructor(packetPlayOutBlockChangeClass, MethodType.methodType(void.class, blockPos, BlockState)); - getIBlockData = lookup.findStatic(CraftMagicNumbers, "getBlock", MethodType.methodType(BlockState, Material.class, byte.class)); - sanitizeLines = lookup.findStatic(CraftSign, v(17, "sanitizeLines").orElse("SANITIZE_LINES"), - MethodType.methodType(toArrayClass(IChatBaseComponent), String[].class)); - - tileEntitySign = lookup.findConstructor(TileEntitySign, MethodType.methodType(void.class, blockPos, BlockState)); - tileEntitySign_getUpdatePacket = XReflection.of(TileEntitySign).method().returns(PacketPlayOutTileEntityData) - .map(MinecraftMapping.MOJANG, "getUpdatePacket") - .map(MinecraftMapping.SPIGOT, v(20, 5, "l").v(20, 4, "m").v(20, "j").v(19, "f").v(18, "c").orElse("getUpdatePacket")) - .unreflect(); - - if (supports(20)) { - Class SignText = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.level.block.entity") - .named("SignText").unreflect(); - // public boolean a(SignText signtext, boolean flag) { - // return flag ? this.c(signtext) : this.b(signtext); - // } - if (!supports(20, 6)) { // It completely changed, needs a lot of work - tileEntitySign_setLine = lookup.findVirtual(TileEntitySign, "a", - MethodType.methodType(boolean.class, SignText, boolean.class)); - } - - Class IChatBaseComponentArray = XReflection.of(IChatBaseComponent).asArray().unreflect(); - - // public SignText(net.minecraft.network.chat.IChatBaseComponent[] var0, IChatBaseComponent[] var1, - // EnumColor var2, boolean var3) { - Class EnumColor = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.item") - .map(MinecraftMapping.MOJANG, "DyeColor") - .map(MinecraftMapping.SPIGOT, "EnumColor").unreflect(); - signText = lookup.findConstructor(SignText, MethodType.methodType(void.class, - IChatBaseComponentArray, IChatBaseComponentArray, EnumColor, boolean.class)); - } else { - tileEntitySign_setLine = lookup.findVirtual(TileEntitySign, "a", MethodType.methodType(void.class, int.class, IChatBaseComponent, IChatBaseComponent)); - } - } - } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException ex) { - ex.printStackTrace(); - } - - GET_ENTITY_HANDLE = getHandle; - GET_DATA_WATCHER = getDataWatcher; - DATA_WATCHER_GET_ITEM = dataWatcherGetItem; - DATA_WATCHER_SET_ITEM = dataWatcherSetItem; - EXP_PACKET = expPacket; - ENTITY_PACKET = entityPacket; - WORLD_HANDLE = worldHandle; - ENTITY_HANDLE = entityHandle; - LIGHTNING_ENTITY = lightning; - VEC3D = vec3D; - PACKET_PLAY_OUT_OPEN_SIGN_EDITOR = signEditorPacket; - PACKET_PLAY_OUT_BLOCK_CHANGE = packetPlayOutBlockChange; - - ANIMATION_PACKET = animationPacket; - ANIMATION_TYPE = animationType; - ANIMATION_ENTITY_ID = animationEntityId; - - BLOCK_POSITION = blockPosition; - PLAY_BLOCK_ACTION = playBlockAction; - GET_BLOCK_TYPE = getBlockType; - GET_BLOCK = getBlock; - GET_IBlockState = getIBlockData; - SANITIZE_LINES = sanitizeLines; - TILE_ENTITY_SIGN = tileEntitySign; - TILE_ENTITY_SIGN__GET_UPDATE_PACKET = tileEntitySign_getUpdatePacket; - TILE_ENTITY_SIGN__SET_LINE = tileEntitySign_setLine; - - GET_BUKKIT_ENTITY = getBukkitEntity; - PLAY_OUT_MULTI_BLOCK_CHANGE_PACKET = playOutMultiBlockChange; - MULTI_BLOCK_CHANGE_INFO = multiBlockChangeInfo; - CHUNK_WRAPPER = chunkWrapper; - CHUNK_WRAPPER_SET = chunkWrapperSet; - SHORTS_OR_INFO = shortsOrInfo; - SET_BlockState = setBlockData; - SIGN_TEXT = signText; - } - - private NMSExtras() { - } - - public static void setExp(Player player, float bar, int lvl, int exp) { - try { - Object packet = EXP_PACKET.invoke(bar, lvl, exp); - MinecraftConnection.sendPacket(player, packet); - } catch (Throwable ex) { - ex.printStackTrace(); - } - } - - public static void lightning(Player player, Location location, boolean sound) { - lightning(Collections.singletonList(player), location, sound); - } - - /** - * https://minecraft.wiki/w/Damage#Lightning_damage - * Lightnings deal 5 damage. - * - * @param players the players to send the packet to. - * @param location the location to spawn the lightning. - * @param sound if the lightning should have a sound or be silent. - */ - public static void lightning(Collection players, Location location, boolean sound) { - try { - Object world = WORLD_HANDLE.invoke(location.getWorld()); - - if (!supports(16)) { - // I don't know what the isEffect and isSilent params are used for. - // It doesn't seem to visually change the lightning. - Object lightningBolt = LIGHTNING_ENTITY.invoke(world, location.getX(), location.getY(), location.getZ(), false, false); - Object packet = ENTITY_PACKET.invoke(lightningBolt); - - for (Player player : players) { - if (sound) XSound.ENTITY_LIGHTNING_BOLT_THUNDER.record().soundPlayer().forPlayers(player).play(); - MinecraftConnection.sendPacket(player, packet); - } - } else { - Class nmsEntityType = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .map(MinecraftMapping.MOJANG, "EntityType") - .map(MinecraftMapping.SPIGOT, "EntityTypes").unreflect(); - - Object lightningType = nmsEntityType.getField(supports(17) ? "U" : "LIGHTNING_BOLT").get(nmsEntityType); - Object lightningBolt = LIGHTNING_ENTITY.invoke(lightningType, world); - Object lightningBoltID = lightningBolt.getClass().getMethod("getId").invoke(lightningBolt); - Object lightningBoltUUID = lightningBolt.getClass().getMethod("getUniqueID").invoke(lightningBolt); - Object vec3D = VEC3D.invoke(0D, 0D, 0D); - Object packet = ENTITY_PACKET.invoke(lightningBoltID, lightningBoltUUID, location.getX(), location.getY(), location.getZ(), 0F, 0F, lightningType, 0, vec3D); - - for (Player player : players) { - if (sound) XSound.ENTITY_LIGHTNING_BOLT_THUNDER.record().soundPlayer().forPlayers(player).play(); - MinecraftConnection.sendPacket(player, packet); - } - } - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - public static Object getData(Object dataWatcher, Object dataWatcherObject) { - try { - return DATA_WATCHER_GET_ITEM.invoke(dataWatcher, dataWatcherObject); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - @Nullable - public static Object getEntityHandle(Entity entity) { - Objects.requireNonNull(entity, "Cannot get handle of null entity"); - try { - return GET_ENTITY_HANDLE.invoke(entity); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - public static Object getDataWatcher(Object handle) { - try { - return GET_DATA_WATCHER.invoke(handle); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - public static Object setData(Object dataWatcher, Object dataWatcherObject, Object value) { - try { - return DATA_WATCHER_SET_ITEM.invoke(dataWatcher, dataWatcherObject, value); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - public static Object getStaticFieldIgnored(Class clazz, String name) { - return getStaticField(clazz, name, true); - } - - public static Object getStaticField(Class clazz, String name, boolean silent) { - try { - Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - return field.get(null); - } catch (Throwable e) { - if (!silent) throw new RuntimeException(e); - else return null; - } - } - - public enum EntityPose { - STANDING("a"), - FALL_FLYING("b"), - SLEEPING("c"), - SWIMMING("d"), - SPIN_ATTACK("e"), - CROUCHING("f"), - LONG_JUMPING("g"), - DYING("h"), - CROAKING("i"), - USING_TONGUE("j"), - SITTING("k"), - ROARING("l"), - SNIFFING("m"), - EMERGING("n"), - DIGGING("o"), - ; - - public final Object enumValue; - private final boolean supported; - - EntityPose(String fieldName) { - boolean supported = true; - Object enumValue = null; - - try { - Class EntityPose = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.entity") - .map(MinecraftMapping.MOJANG, "Pose") - .map(MinecraftMapping.SPIGOT, "EntityPose") - .reflect(); - enumValue = EntityPose.getDeclaredField(v(17, fieldName).orElse(name())).get(null); - } catch (Throwable e) { - supported = false; - } - - this.supported = supported; - this.enumValue = enumValue; - } - - public boolean isSupported() { - return supported; - } - - public Object getEnumValue() { - return enumValue; - } - } - - public enum DataWatcherItemType { - // protected static final DataWatcherObject DATA_LIVING_ENTITY_FLAGS = DataWatcher.defineId(EntityLiving.class, DataWatcherRegistry.BYTE); - DATA_LIVING_ENTITY_FLAGS(getStaticFieldIgnored(EntityLiving, "t")); - - private final Object id; - - private final boolean supported; - - DataWatcherItemType(Object DataWatcherObject) { - boolean supported = true; - Object id = null; - - try { - // public int a() { return this.a; } - // Method idMethod = DataWatcherObject.getClass().getMethod("a"); - id = DataWatcherObject; - } catch (Throwable e) { - supported = false; - } - - this.supported = supported; - this.id = id; - } - - public boolean isSupported() { - return supported; - } - - public Object getId() { - return id; - } - } - - public enum LivingEntityFlags { - SPIN_ATTACK(0x04); - - private final byte bit; - - LivingEntityFlags(int bit) { - this.bit = (byte) bit; - } - - public byte getBit() { - return bit; - } - } - - public static void spinEntity(LivingEntity entity, boolean enabled) { - if (!EntityPose.SPIN_ATTACK.isSupported()) { - throw new UnsupportedOperationException("Spin attacks are not supported in " + getVersionInformation()); - } - - // https://www.spigotmc.org/threads/trident-spinning-animation-riptide.426086/ - // https://www.spigotmc.org/threads/using-the-riptide-animation.469207/ - // https://wiki.vg/Entity_metadata#Living_Entity - // Referenced as "Riptiding" or "AutoSpinAttack" modes in code. - // EntityLiving.r(int ticks) doesn't exist in newer versions. - - // EntityLiving entityLiv = ((CraftPlayer) entity).getHandle(); - // DataWatcher dataWatcher = entityLiv.al(); - // dataWatcher.b((DataWatcherObject) DataWatcherItemType.DATA_LIVING_ENTITY_FLAGS.getId(), (byte) 0x04); - - setLivingEntityFlag(entity, LivingEntityFlags.SPIN_ATTACK.getBit(), enabled); - } - - public static void setLivingEntityFlag(Entity entity, int index, boolean flag) { - Object handle = getEntityHandle(entity); - Object dataWatcher = getDataWatcher(handle); - - Object flagItem = DataWatcherItemType.DATA_LIVING_ENTITY_FLAGS.getId(); - byte currentFlags = (byte) getData(dataWatcher, flagItem); - int newFlags; - - if (flag) { - newFlags = currentFlags | index; - } else { - newFlags = currentFlags & ~index; - } - - setData(dataWatcher, flagItem, (byte) newFlags); - } - - public static boolean hasLivingEntityFlag(Entity entity, int index) { - Object handle = getEntityHandle(entity); - Object dataWatcher = getDataWatcher(handle); - byte flags = (byte) getData(dataWatcher, DataWatcherItemType.DATA_LIVING_ENTITY_FLAGS.getId()); - return (flags & index) != 0; - } - - public boolean isAutoSpinAttack(LivingEntity entity) { - return hasLivingEntityFlag(entity, LivingEntityFlags.SPIN_ATTACK.getBit()); - } - - /** - * For the trident riptide animation use {@link #spinEntity(LivingEntity, boolean)} instead. - */ - public static void animation(Collection players, LivingEntity entity, Animation animation) { - try { - // https://wiki.vg/Protocol#Entity_Animation_.28clientbound.29 - Object packet; - if (supports(17)) packet = ANIMATION_PACKET.invoke(ENTITY_HANDLE.invoke(entity), animation.ordinal()); - else { - packet = ANIMATION_PACKET.invoke(); - ANIMATION_TYPE.invoke(packet, animation.ordinal()); - ANIMATION_ENTITY_ID.invoke(packet, entity.getEntityId()); - } - - for (Player player : players) MinecraftConnection.sendPacket(player, packet); - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - public static void chest(Block chest, boolean open) { - Location location = chest.getLocation(); - try { - Object world = WORLD_HANDLE.invoke(location.getWorld()); - Object position = v(19, - () -> - { - try { - return BLOCK_POSITION.invoke(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }).orElse( - () -> - { - try { - return BLOCK_POSITION.invoke(location.getX(), location.getY(), location.getZ()); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }); - Object block = GET_BLOCK.invoke(GET_BLOCK_TYPE.invoke(world, position)); - PLAY_BLOCK_ACTION.invoke(world, position, block, 1, open ? 1 : 0); - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - /** - * Not completed yet. I have no idea. - */ - @Deprecated - protected static void sendBlockChange(Player player, Chunk chunk, Map blocks) { - try { - Object packet = PLAY_OUT_MULTI_BLOCK_CHANGE_PACKET.invoke(); - - if (supports(16)) { - Object wrapper = CHUNK_WRAPPER.invoke(chunk.getX(), chunk.getZ()); - CHUNK_WRAPPER_SET.invoke(wrapper); - - Object dataArray = Array.newInstance(BlockState, blocks.size()); - Object shortArray = Array.newInstance(short.class, blocks.size()); - - int i = 0; - for (Map.Entry entry : blocks.entrySet()) { - Block loc = entry.getKey().block; - int x = loc.getX() & 15; - int y = loc.getY() & 15; - int z = loc.getZ() & 15; - i++; - } - - SHORTS_OR_INFO.invoke(packet, shortArray); - SET_BlockState.invoke(packet, dataArray); - } else { - Object wrapper = CHUNK_WRAPPER.invoke(chunk.getX(), chunk.getZ()); - CHUNK_WRAPPER_SET.invoke(wrapper); - - Object array = Array.newInstance(MULTI_BLOCK_CHANGE_INFO_CLASS, blocks.size()); - int i = 0; - for (Map.Entry entry : blocks.entrySet()) { - Block loc = entry.getKey().block; - int x = loc.getX() & 15; - int z = loc.getZ() & 15; - i++; - } - - SHORTS_OR_INFO.invoke(packet, array); - } - - MinecraftConnection.sendPacket(player, packet); - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - /** - * Currently only supports 1.17 - */ - public static void openSign(Player player, DyeColor textColor, String[] lines, boolean frontSide) { - try { - Location loc = player.getLocation(); - Object position = BLOCK_POSITION.invoke(loc.getBlockX(), 1, loc.getBlockY()); - Object signBlockData = GET_IBlockState.invoke(Material.OAK_SIGN, (byte) 0); - Object blockChangePacket = PACKET_PLAY_OUT_BLOCK_CHANGE.invoke(position, signBlockData); - - Object components = SANITIZE_LINES.invoke((Object[]) lines); - Object tileSign = TILE_ENTITY_SIGN.invoke(position, signBlockData); - if (supports(20)) { - // When can we use this without blocks... player.openSign(); - Class EnumColor = ofMinecraft().inPackage(MinecraftPackage.NMS, "world.item") - .map(MinecraftMapping.MOJANG, "DyeColor") - .map(MinecraftMapping.SPIGOT, "EnumColor").unreflect(); - Object enumColor = null; - for (Field field : EnumColor.getFields()) { - Object color = field.get(null); - String colorName = (String) EnumColor.getDeclaredMethod("b").invoke(color); // gets its name - if (textColor.name().equalsIgnoreCase(colorName)) { - enumColor = color; - break; - } - } - - Object signText = SIGN_TEXT.invoke(components, components, enumColor, frontSide); - TILE_ENTITY_SIGN__SET_LINE.invoke(signText, true); - } else { - for (int i = 0; i < lines.length; i++) { - Object component = java.lang.reflect.Array.get(components, i); - TILE_ENTITY_SIGN__SET_LINE.invoke(tileSign, i, component, component); - } - } - Object signLinesUpdatePacket = TILE_ENTITY_SIGN__GET_UPDATE_PACKET.invoke(tileSign); - - Object signPacket = - v(20, PACKET_PLAY_OUT_OPEN_SIGN_EDITOR.invoke(position, true)) - .orElse(PACKET_PLAY_OUT_OPEN_SIGN_EDITOR.invoke(position)); - - MinecraftConnection.sendPacket(player, blockChangePacket, signLinesUpdatePacket, signPacket); - } catch (Throwable x) { - x.printStackTrace(); - } - } - - /** - * Order of this enum should not be changed. - */ - public enum Animation { - SWING_MAIN_ARM, HURT, LEAVE_BED, SWING_OFF_HAND, CRITICAL_EFFECT, MAGIC_CRITICAL_EFFECT; - } - - public static class WorldlessBlockWrapper { - public final Block block; - - public WorldlessBlockWrapper(Block block) { - this.block = block; - } - - @Override - public int hashCode() { - return (block.getY() + block.getZ() * 31) * 31 + block.getX(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof Block)) return false; - - Block other = (Block) obj; - return block.getX() == other.getX() - && block.getY() == other.getY() - && block.getZ() == other.getZ(); - } - } -} diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/parser/ReflectionParser.java b/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/parser/ReflectionParser.java deleted file mode 100644 index 7d87f58c7..000000000 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/reflection/parser/ReflectionParser.java +++ /dev/null @@ -1,281 +0,0 @@ -package taboolib.library.xseries.reflection.parser; - -import taboolib.library.xseries.reflection.ReflectiveHandle; -import taboolib.library.xseries.reflection.ReflectiveNamespace; -import taboolib.library.xseries.reflection.XReflection; -import taboolib.library.xseries.reflection.jvm.*; -import taboolib.library.xseries.reflection.jvm.classes.DynamicClassHandle; -import taboolib.library.xseries.reflection.jvm.classes.PackageHandle; -import taboolib.library.xseries.reflection.minecraft.MinecraftPackage; -import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.ApiStatus; - -import java.time.Duration; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * Note: Currently, if XSeries is included as a library, the @Language annotation doesn't work - * even if you have the sources downloaded, this is unfortunately - * a bug with IntelliJ. - *

- * This class should not be used directly. - *

- * This class is designed to be able to parse Java declarations only in a loose way, as it also - * supports more simplified syntax (for example not requiring semicolons) that doesn't work - * as nicely if you're using IntelliJ because of the highlighting which is greater advantage. - */ -@ApiStatus.Internal -public class ReflectionParser { - private final String declaration; - private Pattern pattern; - private Matcher matcher; - private ReflectiveNamespace namespace; - private Map> cachedImports; - private final Set flags = EnumSet.noneOf(Flag.class); - private static final PackageHandle[] PACKAGE_HANDLES = MinecraftPackage.values(); - - public ReflectionParser(@Language("Java") String declaration) { - this.declaration = declaration; - } - - private enum Flag { - PUBLIC, PROTECTED, PRIVATE, FINAL, TRANSIENT, ABSTRACT, STATIC, NATIVE, SYNCHRONIZED, STRICTFP, VOLATILE; - - @SuppressWarnings("RegExpUnnecessaryNonCapturingGroup") - private static final String FLAGS_REGEX = "(?(?:(?:" + Arrays.stream(Flag.values()) - .map(Enum::name) - .map(x -> x.toLowerCase(Locale.ENGLISH)) - .collect(Collectors.joining("|")) - + ")\\s*)+)?"; - } - - @Language("RegExp") - private static final String - GENERIC = "(?:\\s*<\\s*[.\\w<>\\[\\], ]+\\s*>)?", - ARRAY = "((?:\\[])*)", - PACKAGE_REGEX = "(?:package\\s+(?" + PackageHandle.JAVA_PACKAGE_PATTERN + ")\\s*;\\s*)?", - CLASS_TYPES = "(?class|interface|enum)", - PARAMETERS = "\\s*\\(\\s*(?[\\w$_,. ]+)?\\s*\\)", - END_DECL = "\\s*;?\\s*"; - - private static final Pattern - CLASS = Pattern.compile(PACKAGE_REGEX + Flag.FLAGS_REGEX + CLASS_TYPES + "\\s+" + type("className") + - "(?:\\s+extends\\s+" + id("superclasses") + ")?\\s+(implements\\s+" + id("interfaces") + ")?(?:\\s*\\{\\s*})?\\s*"); - private static final Pattern METHOD = Pattern.compile(Flag.FLAGS_REGEX + type("methodReturnType") + "\\s+" - + id("methodName") + PARAMETERS + END_DECL); - private static final Pattern CONSTRUCTOR = Pattern.compile(Flag.FLAGS_REGEX + "\\s+" - + id("className") + PARAMETERS + END_DECL); - private static final Pattern FIELD = Pattern.compile(Flag.FLAGS_REGEX + type("fieldType") + "\\s+" - + id("fieldName") + END_DECL); - - @Language("RegExp") - private static String id(@Language("RegExp") String groupName) { - if (groupName == null) return PackageHandle.JAVA_IDENTIFIER_PATTERN; - return "(?<" + groupName + '>' + PackageHandle.JAVA_IDENTIFIER_PATTERN + ')'; - } - - @SuppressWarnings("LanguageMismatch") - @Language("RegExp") - private static String type(@Language("RegExp") String groupName) { - String type = PackageHandle.JAVA_PACKAGE_PATTERN + GENERIC + ARRAY; - return "(?<" + groupName + '>' + type + ')'; - } - - private Class[] parseTypes(String[] typeNames) { - Class[] classes = new Class[typeNames.length]; - for (int i = 0; i < typeNames.length; i++) { - String typeName = typeNames[i]; - typeName = typeName.trim().substring(0, typeName.lastIndexOf(' ')).trim(); - classes[i] = parseType(typeName); - } - return classes; - } - - private static final Map> PREDEFINED_TYPES = new HashMap<>(); - - static { - Arrays.asList( - byte.class, short.class, int.class, long.class, float.class, double.class, boolean.class, char.class, void.class, - Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, Character.class, Void.class, - Object.class, String.class, CharSequence.class, StringBuilder.class, StringBuffer.class, UUID.class, Optional.class, - Map.class, HashMap.class, ConcurrentHashMap.class, LinkedHashMap.class, WeakHashMap.class, - List.class, ArrayList.class, Set.class, HashSet.class, Deque.class, Queue.class, LinkedList.class, - Date.class, Calendar.class, Duration.class - ).forEach(x -> PREDEFINED_TYPES.put(x.getSimpleName(), x)); - } - - private Class parseType(String typeName) { - if (this.cachedImports == null && this.namespace != null) { - this.cachedImports = this.namespace.getImports(); - } - - String firstTypeName = typeName; - typeName = typeName.replace(" ", ""); - int arrayDimension = 0; - if (typeName.endsWith("[]")) { // Arrays - String replaced = typeName.replace("[]", ""); - arrayDimension = (typeName.length() - replaced.length()) / 2; - typeName = replaced; - } - if (typeName.endsWith(">")) { // Generic - typeName = typeName.substring(0, typeName.indexOf('<')); - } - - Class clazz = null; - if (!typeName.contains(".")) { - // Override predefined types - if (cachedImports != null) clazz = this.cachedImports.get(typeName); - if (clazz == null) clazz = PREDEFINED_TYPES.get(typeName); - } - if (clazz == null) { - try { - clazz = Class.forName(typeName); - } catch (ClassNotFoundException ignored) { - } - } - - if (clazz == null) error("Unknown type '" + firstTypeName + "' -> '" + typeName + '\''); - if (arrayDimension != 0) { - clazz = XReflection.of(clazz).asArray(arrayDimension).unreflect(); - } - return clazz; - } - - public ReflectionParser imports(ReflectiveNamespace namespace) { - this.namespace = namespace; - return this; - } - - private void pattern(Pattern pattern, ReflectiveHandle handle) { - this.pattern = pattern; - this.matcher = pattern.matcher(declaration); - start(handle); - } - - public T parseClass(T classHandle) { - pattern(CLASS, classHandle); - - String packageName = group("package"); - if (packageName != null && !packageName.isEmpty()) { - boolean found = false; - for (PackageHandle pkgHandle : PACKAGE_HANDLES) { - String targetPackageName = pkgHandle.packageId().toLowerCase(Locale.ENGLISH); - if (packageName.startsWith(targetPackageName)) { - classHandle.inPackage(pkgHandle, packageName.substring(targetPackageName.length() + 1)); // + 1 for the dot - found = true; - break; - } - } - if (!found) classHandle.inPackage(packageName); - } - - // String classGeneric = parser.group("generic"); - String className = group("className"); - if (className.contains("<")) className = className.substring(0, className.indexOf('<')); - classHandle.named(className); - - return classHandle; - } - - public T parseConstructor(T ctorHandle) { - pattern(CONSTRUCTOR, ctorHandle); - if (has("className")) { - if (!ctorHandle.getClassHandle().getPossibleNames().contains(group("className"))) - error("Wrong class name associated to constructor, possible names: " + ctorHandle.getClassHandle().getPossibleNames()); - } - if (has("parameters")) ctorHandle.parameters(parseTypes(group("parameters").split(","))); - return ctorHandle; - } - - public T parseMethod(T methodHandle) { - pattern(METHOD, methodHandle); - - // String classGeneric = parser.group("generic"); - methodHandle.named(group("methodName").split("\\$")); - methodHandle.returns(parseType(group("methodReturnType"))); - if (has("parameters")) methodHandle.parameters(parseTypes(group("parameters").split(","))); - - return methodHandle; - } - - public T parseField(T fieldHandle) { - pattern(FIELD, fieldHandle); - - // String classGeneric = parser.group("generic"); - fieldHandle.named(group("fieldName").split("\\$")); - fieldHandle.returns(parseType(group("fieldType"))); - - return fieldHandle; - } - - private String group(String groupName) { - return this.matcher.group(groupName); - } - - private boolean has(String groupName) { - String group = group(groupName); - return group != null && !group.isEmpty(); - } - - private void start(ReflectiveHandle handle) { - if (!matcher.matches()) error("Not a " + handle + " declaration"); - parseFlags(); - if (handle instanceof MemberHandle) { - MemberHandle memberHandle = (MemberHandle) handle; - if (!hasOneOf(flags, Flag.PUBLIC, Flag.PROTECTED, Flag.PRIVATE)) { - // No access modifier is set. - // Interface methods are public, no need to make it accessible. - Class clazz = memberHandle.getClassHandle().reflectOrNull(); - if (clazz != null && !clazz.isInterface()) memberHandle.makeAccessible(); // package-private - } else if (hasOneOf(flags, Flag.PRIVATE, Flag.PROTECTED)) { - memberHandle.makeAccessible(); - } - if (handle instanceof FieldMemberHandle) { - if (flags.contains(Flag.FINAL)) ((FieldMemberHandle) handle).asFinal(); - } - if (handle instanceof FlaggedNamedMemberHandle) { - if (flags.contains(Flag.STATIC)) ((FlaggedNamedMemberHandle) handle).asStatic(); - } - } - } - - private void parseFlags() { - if (!has("flags")) return; - String flagsStr = group("flags"); - - for (String flag : flagsStr.split("\\s+")) { - if (!flags.add(Flag.valueOf(flag.toUpperCase()))) { - error("Repeated flag: " + flag); - } - } - - if (containsDuplicates(flags, Flag.PUBLIC, Flag.PROTECTED, Flag.PRIVATE)) { - error("Duplicate visibility flags"); - } - } - - @SafeVarargs - private static boolean containsDuplicates(Collection collection, T... values) { - boolean contained = false; - for (T value : values) { - if (collection.contains(value)) { - if (contained) return true; - else contained = true; - } - } - return false; - } - - @SafeVarargs - private static boolean hasOneOf(Collection collection, T... elements) { - return Arrays.stream(elements).anyMatch(collection::contains); - } - - private void error(String message) { - throw new RuntimeException(message + " in: " + declaration + " (RegEx: " + pattern.pattern() + "), (Imports: " + cachedImports + ')'); - } -} diff --git a/module/bukkit/bukkit-xseries/build.gradle.kts b/module/bukkit/bukkit-xseries/build.gradle.kts index 7c255bd56..681580962 100644 --- a/module/bukkit/bukkit-xseries/build.gradle.kts +++ b/module/bukkit/bukkit-xseries/build.gradle.kts @@ -1,6 +1,12 @@ @file:Suppress("GradlePackageUpdate", "VulnerableLibrariesLocal") dependencies { + compileOnly(project(":common")) + compileOnly(project(":common-platform-api")) + compileOnly(project(":common-util")) + compileOnly(project(":module:basic:basic-configuration")) + compileOnly(project(":module:bukkit:bukkit-util")) + compileOnly(project(":module:minecraft:minecraft-chat")) // 服务端 compileOnly("ink.ptms.core:v12004:12004-minimize:mapped") compileOnly("ink.ptms.core:v11701:11701-minimize:universal") diff --git a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/XItemStack.java b/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XItemStack.java similarity index 98% rename from module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/XItemStack.java rename to module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XItemStack.java index 45a155d61..3ae6cb94c 100644 --- a/module/bukkit/bukkit-xseries-item/src/main/java/taboolib/library/xseries/XItemStack.java +++ b/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XItemStack.java @@ -51,10 +51,10 @@ import org.bukkit.material.SpawnEgg; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.NotNull; +import org.tabooproject.reflex.AnalyseMode; +import org.tabooproject.reflex.Reflex; import taboolib.library.configuration.ConfigurationSection; -import taboolib.library.xseries.profiles.builder.XSkull; -import taboolib.library.xseries.profiles.objects.Profileable; -import taboolib.library.xseries.reflection.XReflection; import taboolib.module.configuration.Configuration; import taboolib.module.configuration.Type; @@ -82,10 +82,10 @@ * @version 7.5.1 * @see XMaterial * @see XPotion - * @see XSkull * @see XEnchantment * @see ItemStack */ +@SuppressWarnings("ALL") public final class XItemStack { public static final ItemFlag[] ITEM_FLAGS = ItemFlag.values(); public static final boolean SUPPORTS_CUSTOM_MODEL_DATA; @@ -122,13 +122,18 @@ public final class XItemStack { private static Object supportsRegistry(String name) { try { - Class registryClass = XReflection.ofMinecraft().inPackage("org.bukkit").named("Registry").reflect(); - return XReflection.of(registryClass) - .field().asStatic().getter().named(name).returns(registryClass) - .reflect().invoke(); + return Reflex.Companion.getProperty(Class.forName("org.bukkit.Registry"), name, true, false, false, AnalyseMode.REFLECTION_ONLY); } catch (Throwable ex) { return null; } +// try { +// Class registryClass = XReflection.ofMinecraft().inPackage("org.bukkit").named("Registry").reflect(); +// return XReflection.of(registryClass) +// .field().asStatic().getter().named(name).returns(registryClass) +// .reflect().invoke(); +// } catch (Throwable ex) { +// return null; +// } } @SuppressWarnings("unchecked") @@ -162,7 +167,7 @@ private static T getRegistryOrEnum(Class typeClass, Object registry, Stri return type; } - @Nonnull + @NotNull private static PatternType getPatternType(@Nullable String name) { return getRegistryOrEnum( PatternType.class, REGISTRY_BANNER_PATTERN, @@ -298,8 +303,9 @@ public static void serialize(@Nonnull ItemStack item, @Nonnull ConfigurationSect config.set(entry, enchant.getValue()); } } else if (meta instanceof SkullMeta) { - String skull = XSkull.of(meta).getProfileString(); - if (skull != null) config.set("skull", skull); + // TODO 不处理头颅 + // String skull = ItemSkullKt.getSkullValue((SkullMeta) meta); + // if (skull != null) config.set("skull", skull); } else if (meta instanceof BannerMeta) { BannerMeta banner = (BannerMeta) meta; ConfigurationSection patterns = config.createSection("patterns"); @@ -330,7 +336,7 @@ public static void serialize(@Nonnull ItemStack item, @Nonnull ConfigurationSect NamespacedKey type = x.getType().getKey(); String typeStr = type.getNamespace() + ':' + type.getKey(); return typeStr + ", " + x.getDuration() + ", " + x.getAmplifier(); - })); + }).collect(Collectors.toList())); if (SUPPORTS_POTION_COLOR && potion.hasColor()) config.set("color", potion.getColor().asRGB()); } else { @@ -720,8 +726,9 @@ public static ItemStack edit(@Nonnull ItemStack item, if (skull != null) { // Since this is also an editing method, allow empty strings to // represent the instruction to completely remove an existing profile. - if (skull.isEmpty()) XSkull.of(meta).profile(Profileable.detect(skull)).removeProfile(); - else XSkull.of(meta).profile(Profileable.detect(skull)).lenient().apply(); + // if (skull.isEmpty()) XSkull.of(meta).profile(Profileable.detect(skull)).removeProfile(); + // else XSkull.of(meta).profile(Profileable.detect(skull)).lenient().apply(); + // TODO 不做处理 } } else if (meta instanceof BannerMeta) { BannerMeta banner = (BannerMeta) meta; diff --git a/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XPotion.java b/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XPotion.java index 1e67ac271..9d6852f50 100644 --- a/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XPotion.java +++ b/module/bukkit/bukkit-xseries/src/main/java/taboolib/library/xseries/XPotion.java @@ -37,6 +37,7 @@ import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Potion type support for multiple aliases. @@ -50,11 +51,12 @@ * Potions: https://minecraft.wiki/w/Potion * * @author Crypto Morin - * @version 4.0.0 + * @version 4.1.0 * @see PotionEffect * @see PotionEffectType * @see PotionType */ +@SuppressWarnings("ALL") public enum XPotion { ABSORPTION("ABSORB"), BAD_OMEN("OMEN_BAD", "PILLAGER"), @@ -89,6 +91,13 @@ public enum XPotion { SPEED("SPRINT", "RUNFAST", "SWIFT", "FAST"), STRENGTH("INCREASE_DAMAGE", "BULL", "STRONG", "ATTACK"), TRIAL_OMEN, + /** + * Special type of effect. Minecraft itself doesn't recognize this as a separate potion type, + * but as a combination of two other potion types. However, Bukkit's {@link PotionType#TURTLE_MASTER} decided + * to add this, whereas {@link PotionEffectType} doesn't have such enum. + * @since Minecraft v1.21 + */ + TURTLE_MASTER, UNLUCK("UNLUCKY"), WATER_BREATHING("WATER_BREATH", "UNDERWATER_BREATHING", "UNDERWATER_BREATH", "AIR"), WEAKNESS("WEAK"), @@ -135,9 +144,20 @@ public enum XPotion { Data.NAMES.put(legacy, this); if (tempType == null) tempType = PotionEffectType.getByName(legacy); } + if (this.name().equals("TURTLE_MASTER")) tempType = findSlowness(); // Bukkit uses this too. this.type = tempType; } + private static PotionEffectType findSlowness() { + // This is here because it's not safe to access other + // enum members inside the constructor. + return Stream.of("SLOWNESS", "SLOW", "SLUGGISH") + .map(PotionEffectType::getByName) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Cannot find slowness potion type")); + } + /** * Attempts to build the string like an enum name.
* Removes all the spaces, numbers and extra non-English characters. Also removes some config/in-game based strings. @@ -480,6 +500,8 @@ public XPotion or(@Nullable XPotion alternatePotion) { */ @Nullable public PotionType getPotionType() { + // This basically just loops all the types and tries to match them + // against a registry. return type == null ? null : PotionType.getByEffect(type); } diff --git a/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/XMaterialUtil.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/Alias.kt similarity index 100% rename from module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/XMaterialUtil.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/Alias.kt diff --git a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/library/xseries/XItemStack.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/XItemStack.kt similarity index 50% rename from module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/library/xseries/XItemStack.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/XItemStack.kt index 43192062a..a7d04b2b1 100644 --- a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/library/xseries/XItemStack.kt +++ b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/library/xseries/XItemStack.kt @@ -5,28 +5,62 @@ import org.bukkit.inventory.ItemStack import taboolib.library.configuration.ConfigurationSection import taboolib.module.chat.colored +/** + * 将 ItemStack 序列化并保存到配置节点中。 + * + * @param node 要保存的配置节点名称 + * @param itemStack 要序列化的 ItemStack 对象 + */ fun ConfigurationSection.setItemStack(node: String, itemStack: ItemStack) { XItemStack.serialize(itemStack, createSection(node)) } +/** + * 从配置节点中反序列化 ItemStack 对象。 + * + * @param node 要读取的配置节点名称 + * @return 反序列化后的 ItemStack 对象,如果节点不存在则返回 null + */ fun ConfigurationSection.getItemStack(node: String): ItemStack? { val section = getConfigurationSection(node) ?: return null return XItemStack.deserialize(section) { it.colored() } } +/** + * 从配置节点中反序列化 ItemStack 对象,并应用自定义转换函数。 + * + * @param node 要读取的配置节点名称 + * @param transfer 用于转换字符串的自定义函数 + * @return 反序列化后的 ItemStack 对象,如果节点不存在则返回 null + */ fun ConfigurationSection.getItemStack(node: String, transfer: (String) -> String): ItemStack? { val section = getConfigurationSection(node) ?: return null return XItemStack.deserialize(section, transfer) } +/** + * 将字符串解析为 Material 对象。 + * + * @return 解析后的 Material 对象,如果无法匹配则返回 STONE + */ fun String.parseToMaterial(): Material { return XMaterial.matchXMaterial(this).orElse(XMaterial.STONE).parseMaterial()!! } +/** + * 将字符串解析为 XMaterial 对象。 + * + * @return 解析后的 XMaterial 对象,如果无法匹配则返回 STONE + */ fun String.parseToXMaterial(): XMaterial { return XMaterial.matchXMaterial(this).orElse(XMaterial.STONE) } +/** + * 将字符串解析为 ItemStack 对象。 + * + * @return 解析后的 ItemStack 对象,如果无法匹配则返回 STONE 的 ItemStack + */ fun String.parseToItemStack(): ItemStack { return XMaterial.matchXMaterial(this).orElse(XMaterial.STONE).parseItem()!! } \ No newline at end of file diff --git a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/BookBuilder.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/BookBuilder.kt similarity index 100% rename from module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/BookBuilder.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/BookBuilder.kt diff --git a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/BukkitBook.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/BukkitBook.kt similarity index 100% rename from module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/BukkitBook.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/BukkitBook.kt diff --git a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt similarity index 94% rename from module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt index 79df54030..7320110de 100644 --- a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt +++ b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemBuilder.kt @@ -16,9 +16,6 @@ import org.bukkit.potion.PotionEffect import org.tabooproject.reflex.Reflex.Companion.getProperty import org.tabooproject.reflex.Reflex.Companion.invokeMethod import taboolib.library.xseries.XMaterial -import taboolib.library.xseries.profiles.builder.XSkull -import taboolib.library.xseries.profiles.objects.ProfileInputType -import taboolib.library.xseries.profiles.objects.Profileable import taboolib.module.chat.colored import java.util.* @@ -200,7 +197,7 @@ open class ItemBuilder { * 构建物品 */ open fun build(): ItemStack { - val itemStack = ItemStack(material) + var itemStack = ItemStack(material) // 数量 itemStack.amount = amount // 耐久(附加值) @@ -240,10 +237,6 @@ open class ItemBuilder { is SkullMeta -> { // 玩家头颅 if (skullOwner != null) itemMeta.owner = skullOwner - if (skullTexture != null) { - // TODO 这里需要兼容 UUID?因为和以前的逻辑不同,UUID 不同会导致物品无法堆叠 - XSkull.of(itemMeta).profile(Profileable.of(ProfileInputType.BASE64, skullTexture!!.texture)).apply() - } } } @@ -280,6 +273,10 @@ open class ItemBuilder { // 返回 itemStack.itemMeta = itemMeta + // 基于 BukkitSkull 工具设置头 + if (itemMeta is SkullMeta && skullTexture != null) { + itemStack = BukkitSkull.applySkull(itemStack, skullTexture!!.texture) + } finishing(itemStack) return itemStack } @@ -330,7 +327,7 @@ open class ItemBuilder { is SkullMeta -> { // 玩家 skullOwner = itemMeta.owner - // TODO 新版 XSkull 不会用 + // 皮肤信息 itemMeta.getSkullValue()?.let { skullTexture = SkullTexture(it) } } } diff --git a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemSkull.kt b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemSkull.kt similarity index 62% rename from module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemSkull.kt rename to module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemSkull.kt index aa889b6c5..194699157 100644 --- a/module/bukkit/bukkit-xseries-item/src/main/kotlin/taboolib/platform/util/ItemSkull.kt +++ b/module/bukkit/bukkit-xseries/src/main/kotlin/taboolib/platform/util/ItemSkull.kt @@ -1,17 +1,24 @@ package taboolib.platform.util import org.bukkit.inventory.meta.SkullMeta -import taboolib.library.xseries.profiles.builder.XSkull import java.nio.charset.StandardCharsets import java.util.* import java.util.regex.Pattern private val pattern = Pattern.compile("(http://.*?)\"") +/** + * 获取头颅的 Base64 编码值。 + * @return 头颅的 Base64 编码值,如果为空则返回 null。 + */ fun SkullMeta.getSkullValue(): String? { - return XSkull.of(this).profileValue + return BukkitSkull.getSkullValue(this).takeIf { it.isNotEmpty() } } +/** + * 获取头颅皮肤的 URL。 + * @return 头颅皮肤的 URL,如果无法获取则返回 null。 + */ fun SkullMeta.getSkinUrl(): String? { val json = Base64.getDecoder().decode(getSkullValue()).toString(StandardCharsets.UTF_8) val matcher = pattern.matcher(json) diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/AutoDataContainer.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/AutoDataContainer.kt new file mode 100644 index 000000000..f0aa0a7ee --- /dev/null +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/AutoDataContainer.kt @@ -0,0 +1,110 @@ +package taboolib.expansion + +import taboolib.common.Inject +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.function.submitAsync + +/** + * 数据库优先容器,定期从数据库取出数据同步给缓存。 + * + * @property user 用户标识 + * @property database 数据库实例 + */ +class AutoDataContainer(val user: String, val database: Database) { + + /** + * 存储用户数据的源 + */ + var source = database[user] + + /** + * 获取指定键的值 + * + * @param key 键 + * @return 对应的值,如果不存在则返回 null + */ + operator fun get(key: String): String? { + return source[key] + } + + /** + * 设置指定键的值 + * + * 设置操作是穿透缓存直接写数据库,然后进行缓存同步。 + * 这里只操作单个 key 的缓存,是因为防止高频率 set 导致频繁读数据库。 + * + * @param key 键 + * @param value 值 + */ + operator fun set(key: String, value: Any) { + database[user, key] = value.toString() + source[key] = value.toString() + } + + /** + * 获取所有键的集合 + * + * @return 键的集合 + */ + fun keys(): Set { + return source.keys + } + + /** + * 获取所有键值对 + * + * @return 键值对映射 + */ + fun values(): Map { + return source + } + + /** + * 获取键值对的数量 + * + * @return 键值对的数量 + */ + fun size(): Int { + return source.size + } + + /** + * 返回对象的字符串表示 + * + * @return 对象的字符串表示 + */ + override fun toString(): String { + return "AutoDataContainer(user='$user', source=$source)" + } + + /** + * 手动更新数据 + * + * 可以酌情使用 + */ + fun update() { + source = database[user] + } + + @Inject + internal companion object { + + /** + * 延迟更新时间 + */ + var syncTick = 80L + + /** + * 定期更新所有 AutoDataContainer 实例的数据 + */ + @Awake(LifeCycle.ACTIVE) + fun update() { + submitAsync(period = syncTick) { + playerAutoDataContainer.entries.forEach { + runCatching { it.value.update() } + } + } + } + } +} \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/DataContainer.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/DataContainer.kt index 51bb613bf..cda8b4559 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/DataContainer.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/DataContainer.kt @@ -7,47 +7,105 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit /** - * Zaphkiel - * ink.ptms.zaphkiel.module.Vars + * 缓存优先容器 Cache-First + * 用于缓存数据并在一定时间后写入数据库 + * 数据库数据不同步给缓存 * - * @author sky - * @since 2021/8/22 7:51 下午 + * @property user 用户标识 + * @property database 数据库实例 */ class DataContainer(val user: String, val database: Database) { + /** 存储用户数据的源 */ val source = database[user] + + /** 存储需要更新的键值对及其更新时间 */ val updateMap = ConcurrentHashMap() + /** + * 设置指定键的值并立即保存 + * + * @param key 键 + * @param value 值 + */ operator fun set(key: String, value: Any) { source[key] = value.toString() save(key) } + /** + * 穿透缓存的写数据库方法 + * + * @param targetUser 目标用户 + * @param key 键 + * @param value 值 + */ + operator fun set(targetUser: String, key: String, value: Any) { + database[targetUser, key] = value.toString() + } + + /** + * 设置指定键的值,并在指定延迟后更新 + * + * @param key 键 + * @param value 值 + * @param delay 延迟时间 + * @param timeUnit 时间单位 + */ fun setDelayed(key: String, value: Any, delay: Long = 3L, timeUnit: TimeUnit = TimeUnit.SECONDS) { source[key] = value.toString() updateMap[key] = System.currentTimeMillis() - timeUnit.toMillis(delay) } + /** + * 获取指定键的值 + * + * @param key 键 + * @return 对应的值,如果不存在则返回 null + */ operator fun get(key: String): String? { return source[key] } + /** + * 获取所有键的集合 + * + * @return 键的集合 + */ fun keys(): Set { return source.keys } + /** + * 获取所有键值对 + * + * @return 键值对映射 + */ fun values(): Map { return source } + /** + * 获取键值对的数量 + * + * @return 键值对的数量 + */ fun size(): Int { return source.size } + /** + * 保存指定键的值到数据库 + * + * @param key 键 + */ fun save(key: String) { submitAsync { database[user, key] = source[key]!! } } + /** + * 检查并更新需要保存的键值对 + */ fun checkUpdate() { updateMap.filterValues { it < System.currentTimeMillis() }.forEach { (t, _) -> updateMap.remove(t) @@ -55,16 +113,27 @@ class DataContainer(val user: String, val database: Database) { } } + /** + * 返回对象的字符串表示 + * + * @return 对象的字符串表示 + */ override fun toString(): String { return "DataContainer(user='$user', source=$source)" } + /** + * 内部伴生对象,用于定期检查更新 + */ @Inject internal companion object { + /** + * 定期检查并更新所有 DataContainer 实例 + */ @Schedule(period = 20) - private fun checkUpdate() { - playerDataContainer.values.forEach { it.checkUpdate() } + fun checkUpdate() { + playerDataContainer.entries.forEach { it.value.checkUpdate() } } } } \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/Database.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/Database.kt index de6caab02..369f29f36 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/Database.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/Database.kt @@ -9,6 +9,9 @@ class Database(val type: Type, val dataSource: DataSource = type.host().createDa type.tableVar().createTable(dataSource) } + /** + * 根据用户获取用户所有的数据 + */ operator fun get(user: String): MutableMap { return type.tableVar().select(dataSource) { rows("key", "value") @@ -18,24 +21,96 @@ class Database(val type: Type, val dataSource: DataSource = type.host().createDa }.toMap(ConcurrentHashMap()) } - operator fun get(user: String, name: String): String? { + /** + * 根据用户和键获取数据 + */ + operator fun get(user: String, key: String): String? { return type.tableVar().select(dataSource) { rows("value") - where("user" eq user and ("key" eq name)) + where("user" eq user and ("key" eq key)) limit(1) }.firstOrNull { getString("value") } } - operator fun set(user: String, name: String, data: String) { - if (get(user, name) == null) { - type.tableVar().insert(dataSource, "user", "key", "value") { value(user, name, data) } + /** + * 设置用户数据 + * 如果数据为空则转为删除操作 + */ + operator fun set(user: String, key: String, data: String) { + if (data.isEmpty()) { + remove(user, key) + return + } + if (get(user, key) == null) { + type.tableVar().insert(dataSource, "user", "key", "value") { + value(user, key, data) + } } else { type.tableVar().update(dataSource) { set("value", data) - where("user" eq user and ("key" eq name)) + where("user" eq user and ("key" eq key)) } } } + + /** + * 查询数据 根据 用户名 与 键 + * 如果数据不存在则返回 null + */ + fun getValue(user: String, key: String): String? { + return type.tableVar().select(dataSource) { + rows("key", "value") + where("user" eq user and ("key" eq key)) + }.firstOrNull { + getString("value") + } + } + + /** + * 返回所有满足 Key = Value 的用户 + */ + fun getUserList(key: String, value: String): List { + return type.tableVar().select(dataSource) { + rows("user") + where("key" eq key and ("value" eq value)) + }.map { + getString("user") + } + } + + /** + * 根据 Key 来返回一个 的Map + */ + fun getListByKey(key: String): MutableMap { + return type.tableVar().select(dataSource) { + rows("user", "value") + where("key" eq key) + }.map { + getString("user") to getString("value") + }.toMap(ConcurrentHashMap()) + } + + /** + * 根据一个 Key 来尾缀模糊查询User的相关数据 + * 例如 key = "title-" 则会查询所有以 "title-" 开头的数据 + */ + fun getLikeKeyList(user: String, key: String): MutableMap { + return type.tableVar().select(dataSource) { + rows("key", "value") + where("user" eq user and ("key" like "${key}%")) + }.map { + getString("key") to getString("value") + }.toMap(ConcurrentHashMap()) + } + + /** + * 删除符合条件的数据 + */ + fun remove(user: String, key: String) { + type.tableVar().delete(dataSource) { + where("user" eq user and ("key" eq key)) + } + } } \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandler.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandler.kt index a63470a06..d3843d497 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandler.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandler.kt @@ -11,10 +11,41 @@ import java.io.File import java.util.* import java.util.concurrent.ConcurrentHashMap +/** + * 玩家数据库实例。 + * + * 该变量用于存储玩家数据库的引用。初始值为 null,表示数据库尚未初始化。 + * 在设置数据库连接后,此变量将被赋予一个 [Database] 实例。 + */ var playerDatabase: Database? = null +/** + * 玩家数据容器。 + * + * 该变量用于存储玩家的数据容器。它是一个线程安全的并发哈希映射, + * 以玩家的 UUID 为键,对应的 [DataContainer] 为值。 + * 这允许快速、安全地访问和修改玩家的数据。 + */ val playerDataContainer = ConcurrentHashMap() +/** + * 玩家自动数据容器。 + * + * 该变量用于存储玩家的自动数据容器。它是一个线程安全的并发哈希映射, + * 以玩家的 UUID 为键,对应的 [AutoDataContainer] 为值。 + * 这允许自动同步和管理玩家的数据,提供了一种更高级的数据处理机制。 + */ +val playerAutoDataContainer = ConcurrentHashMap() + +/** + * 设置玩家数据库 + * + * @param conf 配置部分 + * @param table 表名,默认从配置中获取 + * @param flags 额外的数据库连接标志 + * @param clearFlags 是否清除现有标志 + * @param ssl SSL 模式 + */ fun setupPlayerDatabase( conf: ConfigurationSection, table: String = conf.getString("table", "")!!, @@ -34,6 +65,19 @@ fun setupPlayerDatabase( playerDatabase = Database(TypeSQL(hostSQL, table)) } +/** + * 设置玩家数据库 + * + * @param host 主机地址 + * @param port 端口号 + * @param user 用户名 + * @param password 密码 + * @param database 数据库名 + * @param table 表名 + * @param flags 额外的数据库连接标志 + * @param clearFlags 是否清除现有标志 + * @param ssl SSL 模式 + */ fun setupPlayerDatabase( host: String = "localhost", port: Int = 3306, @@ -49,31 +93,130 @@ fun setupPlayerDatabase( setupPlayerDatabase(conf, table, flags, clearFlags, ssl) } +/** + * 构建玩家数据库 + * + * @param conf 配置部分 + * @param table 表名,默认从配置中获取 + * @param flags 额外的数据库连接标志 + * @param clearFlags 是否清除现有标志 + * @param ssl SSL 模式 + * @return 数据库实例 + */ +fun buildPlayerDatabase( + conf: ConfigurationSection, + table: String = conf.getString("table", "")!!, + flags: List = emptyList(), + clearFlags: Boolean = false, + ssl: String? = null, +): Database { + val hostSQL = HostSQL(conf) + if (clearFlags) { + hostSQL.flags.clear() + } + hostSQL.flags.addAll(flags) + if (ssl != null) { + hostSQL.flags -= "useSSL=false" + hostSQL.flags += "sslMode=$ssl" + } + return Database(TypeSQL(hostSQL, table)) +} + +/** + * 设置玩家 SQLite 数据库 + * + * @param file 数据库文件 + */ fun setupPlayerDatabase(file: File = newFile(getDataFolder(), "data.db")) { playerDatabase = Database(TypeSQLite(file)) } +/** + * 设置玩家 SQLite 数据库 + * + * @param file 数据库文件 + * @param tableName 表名 + */ +fun setupPlayerDatabase(file: File = newFile(getDataFolder(), "data.db"), tableName: String) { + playerDatabase = Database(TypeSQLite(file, tableName)) +} + +/** + * 构建玩家 SQLite 数据库 + * + * @param file 数据库文件 + * @param table 表名 + * @return 数据库实例 + */ +fun buildPlayerDatabase(file: File = newFile(getDataFolder(), "data.db"), table: String): Database { + return Database(TypeSQLite(file, table)) +} + +/** + * 获取玩家的数据容器 + * + * @return 数据容器 + * @throws IllegalStateException 如果数据容器不可用 + */ fun ProxyPlayer.getDataContainer(): DataContainer { return playerDataContainer[uniqueId] ?: error("unavailable") } +/** + * 为玩家设置数据容器 + * + * @param usernameMode 是否使用用户名模式 + */ fun ProxyPlayer.setupDataContainer(usernameMode: Boolean = false) { val user = if (usernameMode) name else uniqueId.toString() playerDataContainer[uniqueId] = DataContainer(user, playerDatabase!!) } +/** + * 为 UUID 设置玩家数据容器 + */ fun UUID.setupPlayerDataContainer() { playerDataContainer[this] = DataContainer(this.toString(), playerDatabase!!) } +/** + * 获取 UUID 对应的玩家数据容器 + * + * @return 数据容器 + * @throws IllegalStateException 如果数据容器不可用 + */ fun UUID.getPlayerDataContainer(): DataContainer { return playerDataContainer[this] ?: error("unavailable") } +/** + * 释放 UUID 对应的玩家数据容器 + */ fun UUID.releasePlayerDataContainer() { playerDataContainer.remove(this) } +/** + * 释放玩家的数据容器 + */ fun ProxyPlayer.releaseDataContainer() { playerDataContainer.remove(uniqueId) +} + +/** + * 获取 UUID 对应的自动数据容器 + * + * @return 自动数据容器 + */ +fun UUID.getAutoDataContainer(): AutoDataContainer { + return playerAutoDataContainer.computeIfAbsent(this) { + AutoDataContainer(this.toString(), playerDatabase!!) + } +} + +/** + * 释放 UUID 对应的自动数据容器 + */ +fun UUID.releaseAutoDataContainer() { + playerAutoDataContainer.remove(this) } \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandlerForBukkit.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandlerForBukkit.kt index 0ca861314..75d9963d6 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandlerForBukkit.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/DatabaseHandlerForBukkit.kt @@ -3,14 +3,43 @@ package taboolib.expansion import org.bukkit.entity.Player import taboolib.common.platform.function.adaptPlayer +/** + * 获取玩家的数据容器。 + * + * @return 玩家的数据容器 + */ fun Player.getDataContainer(): DataContainer { return adaptPlayer(this).getDataContainer() } +/** + * 为玩家设置数据容器。 + * + * @param usernameMode 是否使用用户名模式,默认为 false + */ fun Player.setupDataContainer(usernameMode: Boolean = false) { adaptPlayer(this).setupDataContainer(usernameMode) } +/** + * 释放玩家的数据容器。 + */ fun Player.releaseDataContainer() { adaptPlayer(this).releaseDataContainer() +} + +/** + * 获取玩家的自动数据容器。 + * + * @return 玩家的自动数据容器 + */ +fun Player.getAutoDataContainer(): AutoDataContainer { + return this.uniqueId.getAutoDataContainer() +} + +/** + * 释放玩家的自动数据容器。 + */ +fun Player.releaseAutoDataContainer() { + this.uniqueId.releaseAutoDataContainer() } \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandler.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandler.kt new file mode 100644 index 000000000..7ff9fcbe8 --- /dev/null +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandler.kt @@ -0,0 +1,125 @@ +package taboolib.expansion + +import taboolib.common.io.newFile +import taboolib.common.platform.function.getDataFolder +import taboolib.common.platform.function.pluginId +import taboolib.common.platform.function.submitAsync +import taboolib.expansion.AutoDataContainer.Companion.syncTick +import taboolib.library.configuration.ConfigurationSection +import java.util.concurrent.ConcurrentHashMap + +/** + * 多表处理器 + * 可以处理更多数据表 + */ +class MultipleHandler( + val conf: ConfigurationSection, + table: String = conf.getString("table", pluginId)!!, + flags: List = emptyList(), + clearFlags: Boolean = false, + ssl: String? = null, + dataFile: String = "data.db", + // 是否挂钩玩家的登录与退出, 用于自动创建与销毁数据容器 键是玩家的UUID.toString() + autoHook: Boolean = false, + syncTick: Long = 80L, +) { + + val database: Database + val databaseContainer = ConcurrentHashMap() + val autoDataContainer = ConcurrentHashMap() + + /** + * 任务终止同步功能 + */ + private var taskFlag = false + + init { + database = if (conf.getBoolean("enable")) { + buildPlayerDatabase(conf, table, flags, clearFlags, ssl) + } else { + buildPlayerDatabase(newFile(getDataFolder(), dataFile), table) + } + if (autoHook) { + MultipleHandlerListener.hooks.add(this) + } + submitAsync(period = syncTick) { + if (taskFlag) { + cancel() + return@submitAsync + } + updateAutoDataContainer() + } + } + + /** + * 停止同步AutoDataContainer + */ + fun stopSync() { + taskFlag = true + } + + /** + * 重新启动同步AutoDataContainer + */ + fun restartSync() { + taskFlag = false + submitAsync(period = syncTick) { + if (taskFlag) { + cancel() + return@submitAsync + } + updateAutoDataContainer() + } + } + + /** + * 初始化数据容器 + * 这个user作为索引,不一定非得是玩家的UUID + */ + fun setupDataContainer(user: String): DataContainer { + return databaseContainer.computeIfAbsent(user) { DataContainer(user, database) } + } + + /** + * 获取数据容器 + */ + fun getDataContainer(user: String): DataContainer { + return databaseContainer[user] ?: error("unavailable database container ${user}") + } + + /** + * 获取数据库优先数据容器 + */ + fun getAutoDataContainer(user: String): AutoDataContainer { + return autoDataContainer.computeIfAbsent(user) { AutoDataContainer(user, database) } + } + + /** + * 移除数据容器 + */ + fun removeDataContainer(user: String) { + databaseContainer.remove(user) + } + + /** + * 移除数据库优先数据容器 + */ + fun removeAutoDataContainer(user: String) { + autoDataContainer.remove(user) + } + + /** + * 移除两种类型的容器 + */ + fun removeContainer(user: String) { + removeDataContainer(user) + removeAutoDataContainer(user) + } + + /** + * 更新所有AutoDataContainer + */ + fun updateAutoDataContainer() { + autoDataContainer.entries.forEach { it.value.update() } + } +} \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandlerListener.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandlerListener.kt new file mode 100644 index 000000000..53169a9c2 --- /dev/null +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/MultipleHandlerListener.kt @@ -0,0 +1,25 @@ +package taboolib.expansion + +import org.bukkit.event.player.PlayerJoinEvent +import taboolib.common.Inject +import taboolib.common.platform.event.SubscribeEvent + +@Inject +object MultipleHandlerListener { + + val hooks = ArrayList() + + @SubscribeEvent + fun onPlayerJoin(event: PlayerJoinEvent) { + hooks.forEach { + it.setupDataContainer(event.player.uniqueId.toString()) + } + } + + @SubscribeEvent + fun onPlayerQuit(event: PlayerJoinEvent) { + hooks.forEach { + it.removeDataContainer(event.player.uniqueId.toString()) + } + } +} \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/Type.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/Type.kt index f0719de9b..b58c0aad6 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/Type.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/Type.kt @@ -3,9 +3,22 @@ package taboolib.expansion import taboolib.module.database.Host import taboolib.module.database.Table +/** + * 抽象类 Type,定义了数据库操作的基本结构。 + */ abstract class Type { + /** + * 获取数据库主机配置。 + * + * @return 返回 Host 对象,表示数据库连接的主机配置。 + */ abstract fun host(): Host<*> + /** + * 获取数据库表配置。 + * + * @return 返回 Table 对象,表示数据库表的配置。 + */ abstract fun tableVar(): Table<*, *> } \ No newline at end of file diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQL.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQL.kt index da584c740..5bb994ab6 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQL.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQL.kt @@ -2,8 +2,17 @@ package taboolib.expansion import taboolib.module.database.* -class TypeSQL(val host: Host, table: String) : Type() { +/** + * TypeSQL 类表示 SQL 数据库的类型实现。 + * + * @property host SQL 数据库的主机配置 + * @property table 数据库表名 + */ +class TypeSQL(val host: Host, val table: String) : Type() { + /** + * 表示数据库表结构的 Table 对象 + */ val tableVar = Table(table, host) { add { id() } add("user") { @@ -21,10 +30,20 @@ class TypeSQL(val host: Host, table: String) : Type() { } } + /** + * 获取数据库主机配置 + * + * @return 返回 Host 对象,表示数据库连接的主机配置 + */ override fun host(): Host<*> { return host } + /** + * 获取数据库表配置 + * + * @return 返回 Table 对象,表示数据库表的配置 + */ override fun tableVar(): Table<*, *> { return tableVar } diff --git a/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQLite.kt b/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQLite.kt index 25962872e..a2cf1eabe 100644 --- a/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQLite.kt +++ b/module/database/database-player/src/main/kotlin/taboolib/expansion/TypeSQLite.kt @@ -8,26 +8,48 @@ import taboolib.module.database.Table import taboolib.module.database.getHost import java.io.File -class TypeSQLite(file: File) : Type() { +/** + * SQLite 数据类型实现类。 + * + * @property file 数据库文件 + * @property tableName 表名,如果为空则使用插件 ID + */ +class TypeSQLite(val file: File, val tableName: String? = null) : Type() { + /** + * SQLite 数据库主机 + */ val host = newFile(file).getHost() - val tableVar = Table(pluginId, host) { + /** + * 数据表结构 + */ + val tableVar = Table(tableName ?: pluginId, host) { add("user") { - type(ColumnTypeSQLite.TEXT, 36) + type(ColumnTypeSQLite.TEXT, 64) } add("key") { type(ColumnTypeSQLite.TEXT, 64) } add("value") { - type(ColumnTypeSQLite.TEXT, 128) + type(ColumnTypeSQLite.TEXT) } } + /** + * 获取数据库主机 + * + * @return 数据库主机 + */ override fun host(): Host<*> { return host } + /** + * 获取数据表结构 + * + * @return 数据表结构 + */ override fun tableVar(): Table<*, *> { return tableVar } diff --git a/settings.gradle.kts b/settings.gradle.kts index bc7ac04fb..b718116e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,6 @@ include( "module:bukkit:bukkit-ui:bukkit-ui-legacy", "module:bukkit:bukkit-util", "module:bukkit:bukkit-xseries", - "module:bukkit:bukkit-xseries-item", // 针对 Bukkit 平台的 NMS 工具 "module:bukkit-nms",