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 extends Player> 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