From 0eb25bcbb0cb800b02daf12f852bf8036889e6d6 Mon Sep 17 00:00:00 2001 From: YuTian <2953516620@qq.com> Date: Sun, 11 May 2025 14:59:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=20RedisBungeeUtil.j?= =?UTF-8?q?ava=20=E6=96=87=E4=BB=B6=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E9=80=BB=E8=BE=91=E9=94=99=E8=AF=AF=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E6=80=A7=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 22 + .../elementoriginlib/ElementOriginLib.java | 7 +- .../command/CommandEntity.java | 56 +- .../command/ICommandManager.java | 2 + .../command/SimpleCommandManager.java | 5 + .../elementoriginlib/command/Suggests.java | 46 + .../command/argument/ArgumentType.java | 12 +- .../command/handler/CommandHandler.java | 46 +- .../command/interfaces/Command.java | 2 + .../command/interfaces/Parameter.java | 2 +- .../command/list/CommandHelp.java | 5 +- .../config/OriginLibConfig.java | 7 + .../{command => }/CommandParseException.java | 2 +- .../ItemStatDataLoadException.java | 2 +- .../expiringmap/EntryLoader.java | 17 - .../expiringmap/ExpirationListener.java | 17 - .../expiringmap/ExpirationPolicy.java | 15 - .../expiringmap/ExpiringEntryLoader.java | 17 - .../expiringmap/ExpiringMap.java | 1431 ----------------- .../expiringmap/ExpiringValue.java | 122 -- .../expiringmap/internal/Assert.java | 32 - .../internal/NamedThreadFactory.java | 27 - .../yutian/elementoriginlib/gui/DebugGui.java | 55 + .../io/yutian/elementoriginlib/gui/Gui.java | 6 +- .../io/yutian/elementoriginlib/gui/IGui.java | 1 + .../yutian/elementoriginlib/gui/PageGui.java | 13 +- .../elementoriginlib/item/TagStatItem.java | 32 + .../item/TagStatItemStackBuilder.java | 20 +- .../elementoriginlib/item/stat/ItemStat.java | 8 - .../elementoriginlib/item/stat/ItemStats.java | 26 +- .../item/stat/type/BooleanStat.java | 29 - .../item/stat/type/DoubleListStat.java | 55 - .../item/stat/type/DoubleStat.java | 31 - .../item/stat/type/EnumListStat.java | 51 - .../item/stat/type/EnumStat.java | 32 - .../item/stat/type/IntListStat.java | 43 - .../item/stat/type/IntStat.java | 45 - .../item/stat/type/MapStat.java | 26 - .../item/stat/type/StringListStat.java | 35 - .../item/stat/type/StringStat.java | 11 - .../item/stat/type/UUIDStat.java | 24 - .../io/yutian/elementoriginlib/lang/Lang.java | 3 + .../elementoriginlib/mongodb/Collection.java | 14 + .../elementoriginlib/mongodb/MongoEntity.java | 9 + .../mongodb/MongoSupport.java | 722 +++++++++ .../mongodb/UpdateEntity.java | 49 + .../yutian/elementoriginlib/point/Point.java | 20 + .../yutian/elementoriginlib/point/Region.java | 8 +- .../scanner/ClassLoaderProcessor.java | 9 + .../scanner/ClassScanner.java | 317 ++++ .../serialize/SerializeHelper.java | 5 +- .../shadowloader/BytecodeRewriter.java | 16 + .../shadowloader/ShadowClassLoader.java | 12 + .../elementoriginlib/util/ClassUtil.java | 7 + .../elementoriginlib/util/ColorUtil.java | 314 ++++ .../util/ItemStackBuilder.java | 424 +++-- .../elementoriginlib/util/LangUtil.java | 6 +- .../elementoriginlib/util/SkullBuilder.java | 23 - .../elementoriginlib/util/StatDataUtil.java | 347 ++++ .../io/yutian/elementoriginlib/util/Util.java | 26 + src/main/resources/mangodb.yml | 0 src/main/resources/plugin.yml | 2 +- 62 files changed, 2357 insertions(+), 2413 deletions(-) rename src/main/java/com/io/yutian/elementoriginlib/exception/{command => }/CommandParseException.java (86%) rename src/main/java/com/io/yutian/elementoriginlib/exception/{itemstat => }/ItemStatDataLoadException.java (87%) delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/EntryLoader.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationListener.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationPolicy.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringEntryLoader.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringMap.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringValue.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/Assert.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/NamedThreadFactory.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/gui/DebugGui.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/mongodb/Collection.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoEntity.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoSupport.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/mongodb/UpdateEntity.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/scanner/ClassLoaderProcessor.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/scanner/ClassScanner.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/shadowloader/BytecodeRewriter.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/shadowloader/ShadowClassLoader.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/util/ColorUtil.java delete mode 100644 src/main/java/com/io/yutian/elementoriginlib/util/SkullBuilder.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/util/StatDataUtil.java create mode 100644 src/main/java/com/io/yutian/elementoriginlib/util/Util.java create mode 100644 src/main/resources/mangodb.yml diff --git a/pom.xml b/pom.xml index d2f7b11..4e3e295 100644 --- a/pom.xml +++ b/pom.xml @@ -137,5 +137,27 @@ jedis 5.2.0 + + + net.jodah + expiringmap + 0.5.11 + + + + org.javassist + javassist + 3.30.2-GA + + + org.mongodb + mongodb-driver-sync + 5.4.0 + + + org.mongodb + mongodb-driver-reactivestreams + 5.4.0 + diff --git a/src/main/java/com/io/yutian/elementoriginlib/ElementOriginLib.java b/src/main/java/com/io/yutian/elementoriginlib/ElementOriginLib.java index 5374bcc..8d768cf 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/ElementOriginLib.java +++ b/src/main/java/com/io/yutian/elementoriginlib/ElementOriginLib.java @@ -8,14 +8,12 @@ import com.io.yutian.elementoriginlib.logger.Logger; import com.io.yutian.elementoriginlib.manager.CommandManager; import com.io.yutian.elementoriginlib.redis.RedisIO; import org.bukkit.plugin.java.JavaPlugin; -import org.slf4j.LoggerFactory; public final class ElementOriginLib extends JavaPlugin { - private static final org.slf4j.Logger log = LoggerFactory.getLogger(ElementOriginLib.class); - private static ElementOriginLib instance; + public static final Logger LOGGER = Logger.getLogger(ElementOriginLib.class); - private static Logger logger = Logger.getLogger(ElementOriginLib.class); + private static ElementOriginLib instance; private RedisIO redisIO; @@ -39,6 +37,7 @@ public final class ElementOriginLib extends JavaPlugin { @Override public void onDisable() { + redisIO.close(); } public void reload() { diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/CommandEntity.java b/src/main/java/com/io/yutian/elementoriginlib/command/CommandEntity.java index e28dcf2..9d2cdb2 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/CommandEntity.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/CommandEntity.java @@ -5,7 +5,7 @@ import com.io.yutian.elementoriginlib.command.argument.ArgumentType; import com.io.yutian.elementoriginlib.command.interfaces.Command; import com.io.yutian.elementoriginlib.command.interfaces.Parameter; import com.io.yutian.elementoriginlib.command.interfaces.SubCommand; -import com.io.yutian.elementoriginlib.exception.command.CommandParseException; +import com.io.yutian.elementoriginlib.exception.CommandParseException; import com.io.yutian.elementoriginlib.logger.Logger; import org.bukkit.command.CommandSender; @@ -20,13 +20,15 @@ public class CommandEntity { private String command; private String permission; + private List senderRequires; private List childrens = new ArrayList<>(); - public CommandEntity(Object instance, String command, String permission, List childrens) { + public CommandEntity(Object instance, String command, String permission, List senderRequires, List childrens) { this.instance = instance; this.command = command; this.permission = permission; + this.senderRequires = senderRequires; this.childrens = childrens; } @@ -46,11 +48,23 @@ public class CommandEntity { return permission; } + public List getSenderRequires() { + return senderRequires; + } + public List getChildrens() { return childrens; } public boolean canInvoke(CommandSender sender) { + if (senderRequires != null && senderRequires.size() > 0) { + for (SenderRequire senderRequire : senderRequires) { + if (senderRequire.test(sender)) { + return true; + } + } + return false; + } if (childrens.size() == 1 && childrens.get(0).isNodal()) { CommandEntry child = childrens.get(0); return child.canInvoke(sender); @@ -74,17 +88,18 @@ public class CommandEntity { throw new CommandParseException("类 " + clazz + " 未标注 @Command 注解"); } -// Object instance; -// try { -// instance = clazz.getConstructor(null).newInstance(); -// } catch (Exception e) { -// e.printStackTrace(); -// throw new CommandParseException("无法实例化类 "+clazz); -// } - Command commandAnnotation = (Command) clazz.getAnnotation(Command.class); String command = commandAnnotation.value(); String permission = commandAnnotation.permission(); + String[] senderRequireArray = commandAnnotation.senderRequire(); + List senderRequireList = new ArrayList<>(); + for (String senderRequire : senderRequireArray) { + SenderRequire senderRequire1 = SenderRequires.get(senderRequire); + if (senderRequire1 == null) { + throwParseException(clazz, "使用者权限 " + senderRequire + " 不存在"); + } + senderRequireList.add(SenderRequires.get(senderRequire)); + } List allEntries = new ArrayList<>(); Map pathToEntryMap = new HashMap<>(); @@ -153,7 +168,7 @@ public class CommandEntity { return null; } - return new CommandEntity(instance, command, permission, rootEntries); + return new CommandEntity(instance, command, permission, senderRequireList, rootEntries); } @@ -222,9 +237,20 @@ public class CommandEntity { Parameter commandArgument = parameter.getAnnotation(Parameter.class); String name = commandArgument.name(); String argumentName = name.isEmpty() ? parameter.getName() : name; - boolean required = commandArgument.required(); - if (!required && index < allArgumentsCount - 1) { - throwParseException(clazz, "可选参数仅能在最后一个位置中使用"); + boolean optional = commandArgument.optional(); + if (optional) { + boolean flag = true; + for (int i = index; i < allArgumentsCount; i++) { + java.lang.reflect.Parameter parameter1 = method.getParameters()[i]; + Parameter parameterAnnotation = parameter.getAnnotation(Parameter.class); + if (!parameterAnnotation.optional()) { + flag = false; + break; + } + } + if (!flag) { + throwParseException(clazz, "可选参数错误, 后续参数必须都是是可选参数"); + } } String defaultValue = commandArgument.defaultValue(); String suggestionType = commandArgument.suggestionType(); @@ -242,7 +268,7 @@ public class CommandEntity { argument.suggest(suggest); } } - if (!required) { + if (optional) { argument.optional(defaultValue); } arguments.add(argument); diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/ICommandManager.java b/src/main/java/com/io/yutian/elementoriginlib/command/ICommandManager.java index 16bd7b5..245ed81 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/ICommandManager.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/ICommandManager.java @@ -6,6 +6,8 @@ public interface ICommandManager { String getName(); + String getPluginId(); + List getCommandEntities(); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/SimpleCommandManager.java b/src/main/java/com/io/yutian/elementoriginlib/command/SimpleCommandManager.java index 81889c5..680a185 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/SimpleCommandManager.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/SimpleCommandManager.java @@ -65,6 +65,11 @@ public class SimpleCommandManager implements ICommandManager { return name; } + @Override + public String getPluginId() { + return name.toLowerCase(); + } + @NotNull @Override public List getCommandEntities() { diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/Suggests.java b/src/main/java/com/io/yutian/elementoriginlib/command/Suggests.java index 743dc35..c1d5776 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/Suggests.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/Suggests.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Supplier; public class Suggests { @@ -41,6 +42,51 @@ public class Suggests { suggests.remove(name); } + public static Suggest createEnumSuggest(Class enumClass) { + return new EnumSuggest(enumClass); + } + + public static Suggest createListSuggest(Supplier> supplier) { + return new ListSuggest(supplier); + } + + private static class EnumSuggest implements Suggest { + + private List suggestions = new LinkedList<>(); + private Class enumClass; + + public EnumSuggest(Class enumClass) { + this.enumClass = enumClass; + if (enumClass != null && enumClass.isEnum()) { + Enum[] constants = enumClass.getEnumConstants(); + if (constants != null) { + for (Enum enumConstant : constants) { + suggestions.add(enumConstant.name()); + } + } + } + } + + @Override + public List getSuggest() { + return suggestions; + } + } + + private static class ListSuggest implements Suggest { + + private Supplier> supplier; + + public ListSuggest(Supplier> supplier) { + this.supplier = supplier; + } + + @Override + public List getSuggest() { + return supplier.get(); + } + } + static { registerSuggest("world_list", WORLD_LIST); diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/argument/ArgumentType.java b/src/main/java/com/io/yutian/elementoriginlib/command/argument/ArgumentType.java index 2e41600..be07ed4 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/argument/ArgumentType.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/argument/ArgumentType.java @@ -16,6 +16,8 @@ public class ArgumentType { private static final ArgumentType DOUBLE = new ArgumentType<>("double", StringUtil::isDouble, Double::parseDouble); + private static final ArgumentType FLOAT = new ArgumentType<>("float", StringUtil::isFloat, Float::parseFloat); + private static final ArgumentType BOOLEAN = new ArgumentType<>("boolean", StringUtil::isBoolean, Boolean::parseBoolean); private static final ArgumentType STRING_ARRAY = new ArgumentType<>("string[]", (s) -> true, (s) -> s.split(",")); @@ -74,6 +76,7 @@ public class ArgumentType { register(String.class, STRING); register(Integer.class, INTEGER); register(Double.class, DOUBLE); + register(Float.class, FLOAT); register(Boolean.class, BOOLEAN); register(String[].class, STRING_ARRAY); register(Integer[].class, INTEGER_ARRAY); @@ -97,20 +100,17 @@ public class ArgumentType { return (ArgumentType) ARGUMENT_TYPES.get(c); } - private static class EnumArgumentType> extends ArgumentType { - - private final Class clazz; + public static class EnumArgumentType> extends ArgumentType { public EnumArgumentType(String name, Class clazz) { super(name, (s)->{ try { - Enum.valueOf(clazz, s); + Enum.valueOf(clazz, s.toUpperCase()); return true; } catch (Exception e) { return false; } - }, (s)-> Enum.valueOf(clazz, s)); - this.clazz = clazz; + }, (s)-> Enum.valueOf(clazz, s.toUpperCase())); } } diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/handler/CommandHandler.java b/src/main/java/com/io/yutian/elementoriginlib/command/handler/CommandHandler.java index 720b4c7..e9e4611 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/handler/CommandHandler.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/handler/CommandHandler.java @@ -74,12 +74,12 @@ public class CommandHandler implements CommandExecutor, TabCompleter { if (!entry.getChildrens().isEmpty()) { fullCommand.append(" "); - handleCommand(commandEntity, sender, label, args, entry.getChildrens(), index + 1, fullCommand); + handleCommand(commandEntity, sender, label, args, entry.getChildrens(), index, fullCommand); } else { - if (!validateArguments(sender, entry.getArguments(), args, index + 1)) { + if (!validateArguments(sender, entry.getArguments(), args, index)) { return; } - Map parsedArguments = parseArgumentValue(sender, entry.getArguments(), args, index + 1); + Map parsedArguments = parseArgumentValue(sender, entry.getArguments(), args, index); entry.invoke(new CommandContext(commandEntity.getInstance(), fullCommand.toString(), label, sender, parsedArguments)); } } @@ -87,7 +87,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter { private boolean validateArguments(CommandSender sender, List arguments, String[] args, int startIndex) { for (int i = 0; i < arguments.size(); i++) { Argument argument = arguments.get(i); - int argIndex = startIndex + i; + int argIndex = startIndex + i + 1; if (argIndex >= args.length) { if (argument.isOptional()) { continue; @@ -109,36 +109,26 @@ public class CommandHandler implements CommandExecutor, TabCompleter { Map parsedArguments = new HashMap<>(); List methodParams = new ArrayList<>(); + int pIndex = startIndex + 1; + for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { Argument argument = arguments.get(argIndex); - if (argIndex >= args.length) { - if (!argument.isOptional()) { - sender.sendMessage(Lang.get("command.short-arg", argument.getName())); - return null; - } - } - String rawValue = null; - if (argument.isOptional() && argIndex >= args.length) { + if (argument.isOptional() && pIndex >= args.length) { rawValue = argument.getDefaultValue(); } else { - rawValue = args[argIndex + startIndex]; - } - if (!argument.getArgumentsType().test(rawValue)) { - sender.sendMessage(Lang.get("command.error-arg", argIndex + 1, rawValue)); - return null; + rawValue = args[pIndex]; } Object parsedValue = argument.getArgumentsType().get(rawValue); parsedArguments.put(argument.getName(), new ArgumentValue(parsedValue)); - methodParams.add(parsedValue); + pIndex++; } return parsedArguments; } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - long startTime = System.currentTimeMillis(); execute(sender, label, args); return true; } @@ -161,21 +151,23 @@ public class CommandHandler implements CommandExecutor, TabCompleter { } CommandEntity commandEntity = entityOptional.get(); - List entries = commandEntity.getChildrens(); - if (entries.isEmpty()) { + if (commandEntity == null) { return Collections.emptyList(); } int depth = args.length; - String currentArg = args[depth - 1]; + int index = depth - 1; - return getSuggestions(sender, entries, args, 0, depth - 1, currentArg); + String currentArg = args[index]; + return getSuggestions(sender, commandEntity, args, 0, index, currentArg); } - private List getSuggestions(CommandSender sender, List entries, String[] args, int start, int currentDepth, String currentArg) { + private List getSuggestions(CommandSender sender, CommandEntity commandEntity, String[] args, int start, int currentDepth, String currentArg) { + CommandEntry commandEntry = null; + List entries = commandEntity.getChildrens(); for (int i = start; i < currentDepth; i++) { String arg = args[i]; @@ -192,10 +184,14 @@ public class CommandHandler implements CommandExecutor, TabCompleter { entries = commandEntry.getChildrens(); } + int pIndex = 2; + if (commandEntry != null && commandEntry.isNodal()) { + pIndex = 1; + } if (entries.size() == 0) { List arguments = commandEntry.getArguments(); - int index = (int) (currentDepth - commandEntry.getDepth() - 2); + int index = (int) (currentDepth - commandEntry.getDepth() - pIndex); if (index >= arguments.size()) { return Collections.emptyList(); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Command.java b/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Command.java index 4073eeb..34a5c26 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Command.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Command.java @@ -13,4 +13,6 @@ public @interface Command { String permission() default "command.elementoriginlib.default"; + String[] senderRequire() default { "console", "player" }; + } diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Parameter.java b/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Parameter.java index f01c70d..59ca103 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Parameter.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/interfaces/Parameter.java @@ -11,7 +11,7 @@ public @interface Parameter { String name() default ""; - boolean required() default true; + boolean optional() default false; String defaultValue() default ""; diff --git a/src/main/java/com/io/yutian/elementoriginlib/command/list/CommandHelp.java b/src/main/java/com/io/yutian/elementoriginlib/command/list/CommandHelp.java index d711028..642ec13 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/command/list/CommandHelp.java +++ b/src/main/java/com/io/yutian/elementoriginlib/command/list/CommandHelp.java @@ -33,7 +33,7 @@ public class CommandHelp { } @SubCommand(nodal = true) - public void help(CommandContext commandContext, @Parameter(required = false, defaultValue = "1") int page) { + public void help(CommandContext commandContext, @Parameter(optional = true, defaultValue = "1") int page) { String alias = commandContext.getLabel(); String commandAlias = alias != null ? alias : commandContext.getLabel(); CommandSender sender = commandContext.getSender(); @@ -87,6 +87,9 @@ public class CommandHelp { } } Optional optional = Lang.getOptional("command."+command.getCommand()+".description"); + if (!optional.isPresent()) { + optional = Lang.getOptional(commandManager.getPluginId()+".command."+command.getCommand()+".description"); + } if (optional.isPresent()) { stringBuilder.append(" "); stringBuilder.append("§7- §f"+ optional.get()); diff --git a/src/main/java/com/io/yutian/elementoriginlib/config/OriginLibConfig.java b/src/main/java/com/io/yutian/elementoriginlib/config/OriginLibConfig.java index 9e54a3a..99b162a 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/config/OriginLibConfig.java +++ b/src/main/java/com/io/yutian/elementoriginlib/config/OriginLibConfig.java @@ -7,6 +7,13 @@ public class OriginLibConfig { private String redisBungeeNetworkId; private String redisBungeeProxyId; + private String mongoDbHost; + private int mongoDbPort; + private String mongoDbDatabase; + private String mongoDbUsername; + private String mongoDbPassword; + + public void load(FileConfiguration config) { redisBungeeNetworkId = config.getString("redisBungeeNetworkId"); redisBungeeProxyId = config.getString("redisBungeeProxyId"); diff --git a/src/main/java/com/io/yutian/elementoriginlib/exception/command/CommandParseException.java b/src/main/java/com/io/yutian/elementoriginlib/exception/CommandParseException.java similarity index 86% rename from src/main/java/com/io/yutian/elementoriginlib/exception/command/CommandParseException.java rename to src/main/java/com/io/yutian/elementoriginlib/exception/CommandParseException.java index 0d7d674..7a8b205 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/exception/command/CommandParseException.java +++ b/src/main/java/com/io/yutian/elementoriginlib/exception/CommandParseException.java @@ -1,4 +1,4 @@ -package com.io.yutian.elementoriginlib.exception.command; +package com.io.yutian.elementoriginlib.exception; public class CommandParseException extends RuntimeException { diff --git a/src/main/java/com/io/yutian/elementoriginlib/exception/itemstat/ItemStatDataLoadException.java b/src/main/java/com/io/yutian/elementoriginlib/exception/ItemStatDataLoadException.java similarity index 87% rename from src/main/java/com/io/yutian/elementoriginlib/exception/itemstat/ItemStatDataLoadException.java rename to src/main/java/com/io/yutian/elementoriginlib/exception/ItemStatDataLoadException.java index ae0f176..56742f0 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/exception/itemstat/ItemStatDataLoadException.java +++ b/src/main/java/com/io/yutian/elementoriginlib/exception/ItemStatDataLoadException.java @@ -1,4 +1,4 @@ -package com.io.yutian.elementoriginlib.exception.itemstat; +package com.io.yutian.elementoriginlib.exception; public class ItemStatDataLoadException extends RuntimeException { diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/EntryLoader.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/EntryLoader.java deleted file mode 100644 index 2b1a850..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/EntryLoader.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -/** - * Loads entries on demand. - * - * @param Key type - * @param Value type - */ -public interface EntryLoader { - /** - * Called to load a new value for the {@code key} into an expiring map. - * - * @param key to load a value for - * @return new value to load - */ - V load(K key); -} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationListener.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationListener.java deleted file mode 100644 index 609d687..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -/** - * A listener for expired object events. - * - * @param Key type - * @param Value type - */ -public interface ExpirationListener { - /** - * Called when a map entry expires. - * - * @param key Expired key - * @param value Expired value - */ - void expired(K key, V value); -} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationPolicy.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationPolicy.java deleted file mode 100644 index f2d7ecb..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpirationPolicy.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -/** - * Determines how ExpiringMap entries should be expired. - */ -public enum ExpirationPolicy { - /** - * Expires entries based on when they were last accessed - */ - ACCESSED, - /** - * Expires entries based on when they were created - */ - CREATED; -} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringEntryLoader.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringEntryLoader.java deleted file mode 100644 index 5cbf29a..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringEntryLoader.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -/** - * Loads entries on demand, with control over each value's expiry duration (i.e. variable expiration). - * - * @param Key type - * @param Value type - */ -public interface ExpiringEntryLoader { - /** - * Called to load a new value for the {@code key} into an expiring map. - * - * @param key to load a value for - * @return contains new value to load along with its expiry duration - */ - ExpiringValue load(K key); -} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringMap.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringMap.java deleted file mode 100644 index 89f8376..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringMap.java +++ /dev/null @@ -1,1431 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -import com.io.yutian.elementoriginlib.expiringmap.internal.Assert; -import com.io.yutian.elementoriginlib.expiringmap.internal.NamedThreadFactory; - -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * A thread-safe map that expires entries. Optional features include expiration policies, variable entry expiration, - * lazy entry loading, and expiration listeners. - * - *

- * Entries are tracked by expiration time and expired by a single thread. - * - *

- * Expiration listeners are called synchronously as entries are expired and block write operations to the map until they - * completed. Asynchronous expiration listeners are called on a separate thread pool and do not block map operations. - * - *

- * When variable expiration is disabled (default), put/remove operations have a time complexity O(1). When - * variable expiration is enabled, put/remove operations have time complexity of O(log n). - * - *

- * Example usages: - * - *

- * {@code
- * Map map = ExpiringMap.create();
- * Map map = ExpiringMap.builder().expiration(30, TimeUnit.SECONDS).build();
- * Map map = ExpiringMap.builder()
- *   .expiration(10, TimeUnit.MINUTES)
- *   .entryLoader(new EntryLoader() {
- *     public Connection load(String address) {
- *       return new Connection(address);
- *     }
- *   })
- *   .expirationListener(new ExpirationListener() {
- *     public void expired(String key, Connection connection) {
- *       connection.close();
- *     }
- *   })
- *   .build();
- * }
- * 
- * - * @param Key type - * @param Value type - * @author Jonathan Halterman - */ -public class ExpiringMap implements ConcurrentMap { - static volatile ScheduledExecutorService EXPIRER; - static volatile ThreadPoolExecutor LISTENER_SERVICE; - static ThreadFactory THREAD_FACTORY; - private final AtomicReference expirationPolicy; - private final EntryLoader entryLoader; - private final ExpiringEntryLoader expiringEntryLoader; - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final Lock readLock = readWriteLock.readLock(); - private final Lock writeLock = readWriteLock.writeLock(); - /** - * Guarded by "readWriteLock" - */ - private final EntryMap entries; - private final boolean variableExpiration; - List> expirationListeners; - List> asyncExpirationListeners; - private AtomicLong expirationNanos; - private int maxSize; - - /** - * Creates a new instance of ExpiringMap. - * - * @param builder The map builder - */ - private ExpiringMap(final Builder builder) { - if (EXPIRER == null) { - synchronized (ExpiringMap.class) { - if (EXPIRER == null) { - EXPIRER = Executors.newSingleThreadScheduledExecutor( - THREAD_FACTORY == null ? new NamedThreadFactory("ExpiringMap-Expirer") : THREAD_FACTORY); - } - } - } - - if (LISTENER_SERVICE == null && builder.asyncExpirationListeners != null) - initListenerService(); - - variableExpiration = builder.variableExpiration; - entries = variableExpiration ? new EntryTreeHashMap() : new EntryLinkedHashMap(); - if (builder.expirationListeners != null) - expirationListeners = new CopyOnWriteArrayList>(builder.expirationListeners); - if (builder.asyncExpirationListeners != null) - asyncExpirationListeners = new CopyOnWriteArrayList>(builder.asyncExpirationListeners); - expirationPolicy = new AtomicReference(builder.expirationPolicy); - expirationNanos = new AtomicLong(TimeUnit.NANOSECONDS.convert(builder.duration, builder.timeUnit)); - maxSize = builder.maxSize; - entryLoader = builder.entryLoader; - expiringEntryLoader = builder.expiringEntryLoader; - } - - /** - * Sets the {@link ThreadFactory} that is used to create expiration and listener callback threads for all ExpiringMap - * instances. - * - * @param threadFactory - * @throws NullPointerException if {@code threadFactory} is null - */ - public static void setThreadFactory(ThreadFactory threadFactory) { - THREAD_FACTORY = Assert.notNull(threadFactory, "threadFactory"); - } - - /** - * Creates an ExpiringMap builder. - * - * @return New ExpiringMap builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Creates a new instance of ExpiringMap with ExpirationPolicy.CREATED and an expiration of 60 seconds. - */ - @SuppressWarnings("unchecked") - public static ExpiringMap create() { - return new ExpiringMap((Builder) ExpiringMap.builder()); - } - - private static Entry mapEntryFor(final ExpiringEntry entry) { - return new Entry() { - @Override - public K getKey() { - return entry.key; - } - - @Override - public V getValue() { - return entry.value; - } - - @Override - public V setValue(V value) { - throw new UnsupportedOperationException(); - } - }; - } - - /** - * Adds an expiration listener. - * - * @param listener to add - * @throws NullPointerException if {@code listener} is null - */ - public synchronized void addExpirationListener(ExpirationListener listener) { - Assert.notNull(listener, "listener"); - if (expirationListeners == null) - expirationListeners = new CopyOnWriteArrayList>(); - expirationListeners.add(listener); - } - - /** - * Adds an asynchronous expiration listener. - * - * @param listener to add - * @throws NullPointerException if {@code listener} is null - */ - public synchronized void addAsyncExpirationListener(ExpirationListener listener) { - Assert.notNull(listener, "listener"); - if (asyncExpirationListeners == null) - asyncExpirationListeners = new CopyOnWriteArrayList>(); - asyncExpirationListeners.add(listener); - // If asyncListener was not added on Builder, LISTENER_SERVICE was not initialized and remain null - if (LISTENER_SERVICE == null) - initListenerService(); - } - - @Override - public void clear() { - writeLock.lock(); - try { - for (ExpiringEntry entry : entries.values()) - entry.cancel(); - entries.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean containsKey(Object key) { - readLock.lock(); - try { - return entries.containsKey(key); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean containsValue(Object value) { - readLock.lock(); - try { - return entries.containsValue(value); - } finally { - readLock.unlock(); - } - } - - /** - * Returns a copy of the map's entries, which can be iterated over safely by multiple threads. - * - * @return Copied set of map entries. - */ - @Override - public Set> entrySet() { - return new AbstractSet>() { - @Override - public void clear() { - ExpiringMap.this.clear(); - } - - @Override - public boolean contains(Object entry) { - if (!(entry instanceof Map.Entry)) - return false; - Entry e = (Entry) entry; - return containsKey(e.getKey()); - } - - @Override - public Iterator> iterator() { - return (entries instanceof EntryLinkedHashMap) ? ((EntryLinkedHashMap) entries).new EntryIterator() - : ((EntryTreeHashMap) entries).new EntryIterator(); - } - - @Override - public boolean remove(Object entry) { - if (entry instanceof Map.Entry) { - Entry e = (Entry) entry; - return ExpiringMap.this.remove(e.getKey()) != null; - } - return false; - } - - @Override - public int size() { - return ExpiringMap.this.size(); - } - }; - } - - @Override - public boolean equals(Object obj) { - readLock.lock(); - try { - return entries.equals(obj); - } finally { - readLock.unlock(); - } - } - - @Override - @SuppressWarnings("unchecked") - public V get(Object key) { - ExpiringEntry entry = getEntry(key); - - if (entry == null) { - return load((K) key); - } else if (ExpirationPolicy.ACCESSED.equals(entry.expirationPolicy.get())) - resetEntry(entry, false); - - return entry.getValue(); - } - - private V load(K key) { - if (entryLoader == null && expiringEntryLoader == null) - return null; - - writeLock.lock(); - try { - // Double check for entry - ExpiringEntry entry = getEntry(key); - if (entry != null) - return entry.getValue(); - - if (entryLoader != null) { - V value = entryLoader.load(key); - put(key, value); - return value; - } else { - ExpiringValue expiringValue = expiringEntryLoader.load(key); - if (expiringValue == null) { - put(key, null); - return null; - } else { - long duration = expiringValue.getTimeUnit() == null ? expirationNanos.get() : expiringValue.getDuration(); - TimeUnit timeUnit = expiringValue.getTimeUnit() == null ? TimeUnit.NANOSECONDS : expiringValue.getTimeUnit(); - put(key, expiringValue.getValue(), expiringValue.getExpirationPolicy() == null ? expirationPolicy.get() - : expiringValue.getExpirationPolicy(), duration, timeUnit); - return expiringValue.getValue(); - } - } - } finally { - writeLock.unlock(); - } - } - - /** - * Returns the map's default expiration duration in milliseconds. - * - * @return The expiration duration (milliseconds) - */ - public long getExpiration() { - return TimeUnit.NANOSECONDS.toMillis(expirationNanos.get()); - } - - /** - * Gets the expiration duration in milliseconds for the entry corresponding to the given key. - * - * @param key - * @return The expiration duration in milliseconds - * @throws NullPointerException if {@code key} is null - * @throws NoSuchElementException If no entry exists for the given key - */ - public long getExpiration(K key) { - Assert.notNull(key, "key"); - ExpiringEntry entry = getEntry(key); - Assert.element(entry, key); - return TimeUnit.NANOSECONDS.toMillis(entry.expirationNanos.get()); - } - - /** - * Gets the ExpirationPolicy for the entry corresponding to the given {@code key}. - * - * @param key - * @return The ExpirationPolicy for the {@code key} - * @throws NullPointerException if {@code key} is null - * @throws NoSuchElementException If no entry exists for the given key - */ - public ExpirationPolicy getExpirationPolicy(K key) { - Assert.notNull(key, "key"); - ExpiringEntry entry = getEntry(key); - Assert.element(entry, key); - return entry.expirationPolicy.get(); - } - - /** - * Gets the expected expiration, in milliseconds from the current time, for the entry corresponding to the given - * {@code key}. - * - * @param key - * @return The expiration duration in milliseconds - * @throws NullPointerException if {@code key} is null - * @throws NoSuchElementException If no entry exists for the given key - */ - public long getExpectedExpiration(K key) { - Assert.notNull(key, "key"); - ExpiringEntry entry = getEntry(key); - Assert.element(entry, key); - return TimeUnit.NANOSECONDS.toMillis(entry.expectedExpiration.get() - System.nanoTime()); - } - - /** - * Gets the maximum size of the map. Once this size has been reached, adding an additional entry will expire the - * first entry in line for expiration based on the expiration policy. - * - * @return The maximum size of the map. - */ - public int getMaxSize() { - return maxSize; - } - - /** - * Sets the maximum size of the map. Once this size has been reached, adding an additional entry will expire the - * first entry in line for expiration based on the expiration policy. - * - * @param maxSize The maximum size of the map. - */ - public void setMaxSize(int maxSize) { - Assert.operation(maxSize > 0, "maxSize"); - this.maxSize = maxSize; - } - - @Override - public int hashCode() { - readLock.lock(); - try { - return entries.hashCode(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean isEmpty() { - readLock.lock(); - try { - return entries.isEmpty(); - } finally { - readLock.unlock(); - } - } - - /** - * Returns a copy of the map's keys, which can be iterated over safely by multiple threads. - * - * @return Copied set of map keys. - */ - @Override - public Set keySet() { - return new AbstractSet() { - @Override - public void clear() { - ExpiringMap.this.clear(); - } - - @Override - public boolean contains(Object key) { - return containsKey(key); - } - - @Override - public Iterator iterator() { - readLock.lock(); - try { - return (entries instanceof EntryLinkedHashMap) ? ((EntryLinkedHashMap) entries).new KeyIterator() - : ((EntryTreeHashMap) entries).new KeyIterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean remove(Object value) { - return ExpiringMap.this.remove(value) != null; - } - - @Override - public int size() { - return ExpiringMap.this.size(); - } - }; - } - - /** - * Puts {@code value} in the map for {@code key}. Resets the entry's expiration unless an entry already exists for the - * same {@code key} and {@code value}. - * - * @param key to put value for - * @param value to put for key - * @return the old value - * @throws NullPointerException if {@code key} is null - */ - @Override - public V put(K key, V value) { - Assert.notNull(key, "key"); - return putInternal(key, value, expirationPolicy.get(), expirationNanos.get()); - } - - /** - * @see #put(Object, Object, ExpirationPolicy, long, TimeUnit) - */ - public V put(K key, V value, ExpirationPolicy expirationPolicy) { - return put(key, value, expirationPolicy, expirationNanos.get(), TimeUnit.NANOSECONDS); - } - - /** - * @see #put(Object, Object, ExpirationPolicy, long, TimeUnit) - */ - public V put(K key, V value, long duration, TimeUnit timeUnit) { - return put(key, value, expirationPolicy.get(), duration, timeUnit); - } - - /** - * Puts {@code value} in the map for {@code key}. Resets the entry's expiration unless an entry already exists for the - * same {@code key} and {@code value}. Requires that variable expiration be enabled. - * - * @param key Key to put value for - * @param value Value to put for key - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @return the old value - * @throws UnsupportedOperationException If variable expiration is not enabled - * @throws NullPointerException if {@code key}, {@code expirationPolicy} or {@code timeUnit} are null - */ - public V put(K key, V value, ExpirationPolicy expirationPolicy, long duration, TimeUnit timeUnit) { - Assert.notNull(key, "key"); - Assert.notNull(expirationPolicy, "expirationPolicy"); - Assert.notNull(timeUnit, "timeUnit"); - Assert.operation(variableExpiration, "Variable expiration is not enabled"); - return putInternal(key, value, expirationPolicy, TimeUnit.NANOSECONDS.convert(duration, timeUnit)); - } - - @Override - public void putAll(Map map) { - Assert.notNull(map, "map"); - long expiration = expirationNanos.get(); - ExpirationPolicy expirationPolicy = this.expirationPolicy.get(); - writeLock.lock(); - try { - for (Entry entry : map.entrySet()) - putInternal(entry.getKey(), entry.getValue(), expirationPolicy, expiration); - } finally { - writeLock.unlock(); - } - } - - @Override - public V putIfAbsent(K key, V value) { - Assert.notNull(key, "key"); - writeLock.lock(); - try { - if (!entries.containsKey(key)) - return putInternal(key, value, expirationPolicy.get(), expirationNanos.get()); - else - return entries.get(key).getValue(); - } finally { - writeLock.unlock(); - } - } - - @Override - public V remove(Object key) { - Assert.notNull(key, "key"); - writeLock.lock(); - try { - ExpiringEntry entry = entries.remove(key); - if (entry == null) - return null; - if (entry.cancel()) - scheduleEntry(entries.first()); - return entry.getValue(); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean remove(Object key, Object value) { - Assert.notNull(key, "key"); - writeLock.lock(); - try { - ExpiringEntry entry = entries.get(key); - if (entry != null && entry.getValue().equals(value)) { - entries.remove(key); - if (entry.cancel()) - scheduleEntry(entries.first()); - return true; - } else - return false; - } finally { - writeLock.unlock(); - } - } - - @Override - public V replace(K key, V value) { - Assert.notNull(key, "key"); - writeLock.lock(); - try { - if (entries.containsKey(key)) { - return putInternal(key, value, expirationPolicy.get(), expirationNanos.get()); - } else - return null; - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - Assert.notNull(key, "key"); - writeLock.lock(); - try { - ExpiringEntry entry = entries.get(key); - if (entry != null && entry.getValue().equals(oldValue)) { - putInternal(key, newValue, expirationPolicy.get(), expirationNanos.get()); - return true; - } else - return false; - } finally { - writeLock.unlock(); - } - } - - /** - * Removes an expiration listener. - * - * @param listener - * @throws NullPointerException if {@code listener} is null - */ - public void removeExpirationListener(ExpirationListener listener) { - Assert.notNull(listener, "listener"); - for (int i = 0; i < expirationListeners.size(); i++) { - if (expirationListeners.get(i).equals(listener)) { - expirationListeners.remove(i); - return; - } - } - } - - /** - * Removes an asynchronous expiration listener. - * - * @param listener - * @throws NullPointerException if {@code listener} is null - */ - public void removeAsyncExpirationListener(ExpirationListener listener) { - Assert.notNull(listener, "listener"); - for (int i = 0; i < asyncExpirationListeners.size(); i++) { - if (asyncExpirationListeners.get(i).equals(listener)) { - asyncExpirationListeners.remove(i); - return; - } - } - } - - /** - * Resets expiration for the entry corresponding to {@code key}. - * - * @param key to reset expiration for - * @throws NullPointerException if {@code key} is null - */ - public void resetExpiration(K key) { - Assert.notNull(key, "key"); - ExpiringEntry entry = getEntry(key); - if (entry != null) - resetEntry(entry, false); - } - - /** - * Sets the expiration duration for the entry corresponding to the given key. Supported only if variable expiration is - * enabled. - * - * @param key Key to set expiration for - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @throws NullPointerException if {@code key} or {@code timeUnit} are null - * @throws UnsupportedOperationException If variable expiration is not enabled - */ - public void setExpiration(K key, long duration, TimeUnit timeUnit) { - Assert.notNull(key, "key"); - Assert.notNull(timeUnit, "timeUnit"); - Assert.operation(variableExpiration, "Variable expiration is not enabled"); - writeLock.lock(); - try { - ExpiringEntry entry = entries.get(key); - if (entry != null) { - entry.expirationNanos.set(TimeUnit.NANOSECONDS.convert(duration, timeUnit)); - resetEntry(entry, true); - } - } finally { - writeLock.unlock(); - } - } - - /** - * Updates the default map entry expiration. Supported only if variable expiration is enabled. - * - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @throws NullPointerException {@code timeUnit} is null - * @throws UnsupportedOperationException If variable expiration is not enabled - */ - public void setExpiration(long duration, TimeUnit timeUnit) { - Assert.notNull(timeUnit, "timeUnit"); - Assert.operation(variableExpiration, "Variable expiration is not enabled"); - expirationNanos.set(TimeUnit.NANOSECONDS.convert(duration, timeUnit)); - } - - /** - * Sets the global expiration policy for the map. Individual expiration policies may override the global policy. - * - * @param expirationPolicy - * @throws NullPointerException {@code expirationPolicy} is null - */ - public void setExpirationPolicy(ExpirationPolicy expirationPolicy) { - Assert.notNull(expirationPolicy, "expirationPolicy"); - this.expirationPolicy.set(expirationPolicy); - } - - /** - * Sets the expiration policy for the entry corresponding to the given key. - * - * @param key to set policy for - * @param expirationPolicy to set - * @throws NullPointerException if {@code key} or {@code expirationPolicy} are null - * @throws UnsupportedOperationException If variable expiration is not enabled - */ - public void setExpirationPolicy(K key, ExpirationPolicy expirationPolicy) { - Assert.notNull(key, "key"); - Assert.notNull(expirationPolicy, "expirationPolicy"); - Assert.operation(variableExpiration, "Variable expiration is not enabled"); - ExpiringEntry entry = getEntry(key); - if (entry != null) - entry.expirationPolicy.set(expirationPolicy); - } - - @Override - public int size() { - readLock.lock(); - try { - return entries.size(); - } finally { - readLock.unlock(); - } - } - - @Override - public String toString() { - readLock.lock(); - try { - return entries.toString(); - } finally { - readLock.unlock(); - } - } - - /** - * Returns a copy of the map's values, which can be iterated over safely by multiple threads. - * - * @return Copied set of map values. - */ - @Override - public Collection values() { - return new AbstractCollection() { - @Override - public void clear() { - ExpiringMap.this.clear(); - } - - @Override - public boolean contains(Object value) { - return containsValue(value); - } - - @Override - public Iterator iterator() { - readLock.lock(); - try { - return (entries instanceof EntryLinkedHashMap) ? ((EntryLinkedHashMap) entries).new ValueIterator() - : ((EntryTreeHashMap) entries).new ValueIterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public int size() { - return ExpiringMap.this.size(); - } - }; - } - - /** - * Notifies expiration listeners that the given entry expired. Must not be called from within a locked context. - * - * @param entry Entry to expire - */ - void notifyListeners(final ExpiringEntry entry) { - if (asyncExpirationListeners != null) - for (final ExpirationListener listener : asyncExpirationListeners) { - LISTENER_SERVICE.execute(new Runnable() { - public void run() { - try { - listener.expired(entry.key, entry.getValue()); - } catch (Exception ignoreUserExceptions) { - } - } - }); - } - - if (expirationListeners != null) - for (final ExpirationListener listener : expirationListeners) { - try { - listener.expired(entry.key, entry.getValue()); - } catch (Exception ignoreUserExceptions) { - } - } - } - - /** - * Returns the internal ExpiringEntry for the {@code key}, obtaining a read lock. - */ - ExpiringEntry getEntry(Object key) { - readLock.lock(); - try { - return entries.get(key); - } finally { - readLock.unlock(); - } - } - - /** - * Puts the given key/value in storage, scheduling the new entry for expiration if needed. If a previous value existed - * for the given key, it is first cancelled and the entries reordered to reflect the new expiration. - */ - V putInternal(K key, V value, ExpirationPolicy expirationPolicy, long expirationNanos) { - writeLock.lock(); - try { - ExpiringEntry entry = entries.get(key); - V oldValue = null; - - if (entry == null) { - entry = new ExpiringEntry(key, value, - variableExpiration ? new AtomicReference(expirationPolicy) : this.expirationPolicy, - variableExpiration ? new AtomicLong(expirationNanos) : this.expirationNanos); - if (entries.size() >= maxSize) { - ExpiringEntry expiredEntry = entries.first(); - entries.remove(expiredEntry.key); - notifyListeners(expiredEntry); - } - entries.put(key, entry); - if (entries.size() == 1 || entries.first().equals(entry)) - scheduleEntry(entry); - } else { - oldValue = entry.getValue(); - if (!ExpirationPolicy.ACCESSED.equals(expirationPolicy) - && ((oldValue == null && value == null) || (oldValue != null && oldValue.equals(value)))) - return value; - - entry.setValue(value); - resetEntry(entry, false); - } - - return oldValue; - } finally { - writeLock.unlock(); - } - } - - /** - * Resets the given entry's schedule canceling any existing scheduled expiration and reordering the entry in the - * internal map. Schedules the next entry in the map if the given {@code entry} was scheduled or if - * {@code scheduleNext} is true. - * - * @param entry to reset - * @param scheduleFirstEntry whether the first entry should be automatically scheduled - */ - void resetEntry(ExpiringEntry entry, boolean scheduleFirstEntry) { - writeLock.lock(); - try { - boolean scheduled = entry.cancel(); - entries.reorder(entry); - - if (scheduled || scheduleFirstEntry) - scheduleEntry(entries.first()); - } finally { - writeLock.unlock(); - } - } - - /** - * Schedules an entry for expiration. Guards against concurrent schedule/schedule, cancel/schedule and schedule/cancel - * calls. - * - * @param entry Entry to schedule - */ - void scheduleEntry(ExpiringEntry entry) { - if (entry == null || entry.scheduled) - return; - - Runnable runnable = null; - synchronized (entry) { - if (entry.scheduled) - return; - - final WeakReference> entryReference = new WeakReference>(entry); - runnable = new Runnable() { - @Override - public void run() { - ExpiringEntry entry = entryReference.get(); - - writeLock.lock(); - try { - if (entry != null && entry.scheduled) { - entries.remove(entry.key); - notifyListeners(entry); - } - - try { - // Expires entries and schedules the next entry - Iterator> iterator = entries.valuesIterator(); - boolean schedulePending = true; - - while (iterator.hasNext() && schedulePending) { - ExpiringEntry nextEntry = iterator.next(); - if (nextEntry.expectedExpiration.get() <= System.nanoTime()) { - iterator.remove(); - notifyListeners(nextEntry); - } else { - scheduleEntry(nextEntry); - schedulePending = false; - } - } - } catch (NoSuchElementException ignored) { - } - } finally { - writeLock.unlock(); - } - } - }; - - Future entryFuture = EXPIRER.schedule(runnable, entry.expectedExpiration.get() - System.nanoTime(), - TimeUnit.NANOSECONDS); - entry.schedule(entryFuture); - } - } - - private void initListenerService() { - synchronized (ExpiringMap.class) { - if (LISTENER_SERVICE == null) { - LISTENER_SERVICE = (ThreadPoolExecutor) Executors.newCachedThreadPool( - THREAD_FACTORY == null ? new NamedThreadFactory("ExpiringMap-Listener-%s") : THREAD_FACTORY); - } - } - } - - /** - * Entry map definition. - */ - private interface EntryMap extends Map> { - /** - * Returns the first entry in the map or null if the map is empty. - */ - ExpiringEntry first(); - - /** - * Reorders the given entry in the map. - * - * @param entry to reorder - */ - void reorder(ExpiringEntry entry); - - /** - * Returns a values iterator. - */ - Iterator> valuesIterator(); - } - - /** - * Builds ExpiringMap instances. Defaults to ExpirationPolicy.CREATED, expiration of 60 TimeUnit.SECONDS and - * a maxSize of Integer.MAX_VALUE. - */ - public static final class Builder { - private ExpirationPolicy expirationPolicy = ExpirationPolicy.CREATED; - private List> expirationListeners; - private List> asyncExpirationListeners; - private TimeUnit timeUnit = TimeUnit.SECONDS; - private boolean variableExpiration; - private long duration = 60; - private int maxSize = Integer.MAX_VALUE; - private EntryLoader entryLoader; - private ExpiringEntryLoader expiringEntryLoader; - - /** - * Creates a new Builder object. - */ - private Builder() { - } - - /** - * Builds and returns an expiring map. - * - * @param Key type - * @param Value type - */ - @SuppressWarnings("unchecked") - public ExpiringMap build() { - return new ExpiringMap((Builder) this); - } - - /** - * Sets the default map entry expiration. - * - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @throws NullPointerException if {@code timeUnit} is null - */ - public Builder expiration(long duration, TimeUnit timeUnit) { - this.duration = duration; - this.timeUnit = Assert.notNull(timeUnit, "timeUnit"); - return this; - } - - /** - * Sets the maximum size of the map. Once this size has been reached, adding an additional entry will expire the - * first entry in line for expiration based on the expiration policy. - * - * @param maxSize The maximum size of the map. - */ - public Builder maxSize(int maxSize) { - Assert.operation(maxSize > 0, "maxSize"); - this.maxSize = maxSize; - return this; - } - - /** - * Sets the EntryLoader to use when loading entries. Either an EntryLoader or ExpiringEntryLoader may be set, not - * both. - * - * @param loader to set - * @throws NullPointerException if {@code loader} is null - * @throws IllegalStateException if an {@link #expiringEntryLoader(ExpiringEntryLoader) ExpiringEntryLoader} is set - */ - @SuppressWarnings("unchecked") - public Builder entryLoader(EntryLoader loader) { - assertNoLoaderSet(); - entryLoader = (EntryLoader) Assert.notNull(loader, "loader"); - return (Builder) this; - } - - /** - * Sets the ExpiringEntryLoader to use when loading entries and configures {@link #variableExpiration() variable - * expiration}. Either an EntryLoader or ExpiringEntryLoader may be set, not both. - * - * @param loader to set - * @throws NullPointerException if {@code loader} is null - * @throws IllegalStateException if an {@link #entryLoader(EntryLoader) EntryLoader} is set - */ - @SuppressWarnings("unchecked") - public Builder expiringEntryLoader( - ExpiringEntryLoader loader) { - assertNoLoaderSet(); - expiringEntryLoader = (ExpiringEntryLoader) Assert.notNull(loader, "loader"); - variableExpiration(); - return (Builder) this; - } - - /** - * Configures the expiration listener that will receive notifications upon each map entry's expiration. - * Notifications are delivered synchronously and block map write operations. - * - * @param listener to set - * @throws NullPointerException if {@code listener} is null - */ - @SuppressWarnings("unchecked") - public Builder expirationListener( - ExpirationListener listener) { - Assert.notNull(listener, "listener"); - if (expirationListeners == null) - expirationListeners = new ArrayList>(); - expirationListeners.add((ExpirationListener) listener); - return (Builder) this; - } - - /** - * Configures the expiration listeners which will receive notifications upon each map entry's expiration. - * Notifications are delivered synchronously and block map write operations. - * - * @param listeners to set - * @throws NullPointerException if {@code listener} is null - */ - @SuppressWarnings("unchecked") - public Builder expirationListeners( - List> listeners) { - Assert.notNull(listeners, "listeners"); - if (expirationListeners == null) - expirationListeners = new ArrayList>(listeners.size()); - for (ExpirationListener listener : listeners) - expirationListeners.add((ExpirationListener) listener); - return (Builder) this; - } - - /** - * Configures the expiration listener which will receive asynchronous notifications upon each map entry's - * expiration. - * - * @param listener to set - * @throws NullPointerException if {@code listener} is null - */ - @SuppressWarnings("unchecked") - public Builder asyncExpirationListener( - ExpirationListener listener) { - Assert.notNull(listener, "listener"); - if (asyncExpirationListeners == null) - asyncExpirationListeners = new ArrayList>(); - asyncExpirationListeners.add((ExpirationListener) listener); - return (Builder) this; - } - - /** - * Configures the expiration listeners which will receive asynchronous notifications upon each map entry's - * expiration. - * - * @param listeners to set - * @throws NullPointerException if {@code listener} is null - */ - @SuppressWarnings("unchecked") - public Builder asyncExpirationListeners( - List> listeners) { - Assert.notNull(listeners, "listeners"); - if (asyncExpirationListeners == null) - asyncExpirationListeners = new ArrayList>(listeners.size()); - for (ExpirationListener listener : listeners) - asyncExpirationListeners.add((ExpirationListener) listener); - return (Builder) this; - } - - /** - * Configures the map entry expiration policy. - * - * @param expirationPolicy - * @throws NullPointerException if {@code expirationPolicy} is null - */ - public Builder expirationPolicy(ExpirationPolicy expirationPolicy) { - this.expirationPolicy = Assert.notNull(expirationPolicy, "expirationPolicy"); - return this; - } - - /** - * Allows for map entries to have individual expirations and for expirations to be changed. - */ - public Builder variableExpiration() { - variableExpiration = true; - return this; - } - - private void assertNoLoaderSet() { - Assert.state(entryLoader == null && expiringEntryLoader == null, - "Either entryLoader or expiringEntryLoader may be set, not both"); - } - } - - /** - * Entry LinkedHashMap implementation. - */ - private static class EntryLinkedHashMap extends LinkedHashMap> - implements EntryMap { - private static final long serialVersionUID = 1L; - - @Override - public boolean containsValue(Object value) { - for (ExpiringEntry entry : values()) { - V v = entry.value; - if (v == value || (value != null && value.equals(v))) - return true; - } - return false; - } - - @Override - public ExpiringEntry first() { - return isEmpty() ? null : values().iterator().next(); - } - - @Override - public void reorder(ExpiringEntry value) { - remove(value.key); - value.resetExpiration(); - put(value.key, value); - } - - @Override - public Iterator> valuesIterator() { - return values().iterator(); - } - - abstract class AbstractHashIterator { - private final Iterator>> iterator; - private ExpiringEntry next; - - @SuppressWarnings({"unchecked", "rawtypes"}) - AbstractHashIterator() { - iterator = (Iterator) Arrays.asList(entrySet().toArray(new Map.Entry[0])).iterator(); - } - - public boolean hasNext() { - return iterator.hasNext(); - } - - public ExpiringEntry getNext() { - next = iterator.next().getValue(); - return next; - } - - public void remove() { - iterator.remove(); - } - } - - final class KeyIterator extends AbstractHashIterator implements Iterator { - public K next() { - return getNext().key; - } - } - - final class ValueIterator extends AbstractHashIterator implements Iterator { - public V next() { - return getNext().value; - } - } - - public final class EntryIterator extends AbstractHashIterator implements Iterator> { - public Map.Entry next() { - return mapEntryFor(getNext()); - } - } - } - - /** - * Entry TreeHashMap implementation for variable expiration ExpiringMap entries. - */ - private static class EntryTreeHashMap extends HashMap> implements EntryMap { - private static final long serialVersionUID = 1L; - SortedSet> sortedSet = new ConcurrentSkipListSet>(); - - @Override - public void clear() { - super.clear(); - sortedSet.clear(); - } - - @Override - public boolean containsValue(Object value) { - for (ExpiringEntry entry : values()) { - V v = entry.value; - if (v == value || (value != null && value.equals(v))) - return true; - } - return false; - } - - @Override - public ExpiringEntry first() { - return sortedSet.isEmpty() ? null : sortedSet.first(); - } - - @Override - public ExpiringEntry put(K key, ExpiringEntry value) { - sortedSet.add(value); - return super.put(key, value); - } - - @Override - public ExpiringEntry remove(Object key) { - ExpiringEntry entry = super.remove(key); - if (entry != null) - sortedSet.remove(entry); - return entry; - } - - @Override - public void reorder(ExpiringEntry value) { - sortedSet.remove(value); - value.resetExpiration(); - sortedSet.add(value); - } - - @Override - public Iterator> valuesIterator() { - return new ExpiringEntryIterator(); - } - - abstract class AbstractHashIterator { - private final Iterator> iterator = sortedSet.iterator(); - protected ExpiringEntry next; - - public boolean hasNext() { - return iterator.hasNext(); - } - - public ExpiringEntry getNext() { - next = iterator.next(); - return next; - } - - public void remove() { - EntryTreeHashMap.super.remove(next.key); - iterator.remove(); - } - } - - final class ExpiringEntryIterator extends AbstractHashIterator implements Iterator> { - public final ExpiringEntry next() { - return getNext(); - } - } - - final class KeyIterator extends AbstractHashIterator implements Iterator { - public final K next() { - return getNext().key; - } - } - - final class ValueIterator extends AbstractHashIterator implements Iterator { - public final V next() { - return getNext().value; - } - } - - final class EntryIterator extends AbstractHashIterator implements Iterator> { - public final Entry next() { - return mapEntryFor(getNext()); - } - } - } - - /** - * Expiring map entry implementation. - */ - static class ExpiringEntry implements Comparable> { - final AtomicLong expirationNanos; - /** - * Epoch time at which the entry is expected to expire - */ - final AtomicLong expectedExpiration; - final AtomicReference expirationPolicy; - final K key; - /** - * Guarded by "this" - */ - volatile Future entryFuture; - /** - * Guarded by "this" - */ - V value; - /** - * Guarded by "this" - */ - volatile boolean scheduled; - - /** - * Creates a new ExpiringEntry object. - * - * @param key for the entry - * @param value for the entry - * @param expirationPolicy for the entry - * @param expirationNanos for the entry - */ - ExpiringEntry(K key, V value, AtomicReference expirationPolicy, AtomicLong expirationNanos) { - this.key = key; - this.value = value; - this.expirationPolicy = expirationPolicy; - this.expirationNanos = expirationNanos; - this.expectedExpiration = new AtomicLong(); - resetExpiration(); - } - - @Override - public int compareTo(ExpiringEntry other) { - if (key.equals(other.key)) - return 0; - return expectedExpiration.get() < other.expectedExpiration.get() ? -1 : 1; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ExpiringEntry other = (ExpiringEntry) obj; - if (!key.equals(other.key)) - return false; - if (value == null) { - if (other.value != null) - return false; - } else if (!value.equals(other.value)) - return false; - return true; - } - - @Override - public String toString() { - return value.toString(); - } - - /** - * Marks the entry as canceled. - * - * @return true if the entry was scheduled - */ - synchronized boolean cancel() { - boolean result = scheduled; - if (entryFuture != null) - entryFuture.cancel(false); - - entryFuture = null; - scheduled = false; - return result; - } - - /** - * Gets the entry value. - */ - synchronized V getValue() { - return value; - } - - /** - * Sets the entry value. - */ - synchronized void setValue(V value) { - this.value = value; - } - - /** - * Resets the entry's expected expiration. - */ - void resetExpiration() { - expectedExpiration.set(expirationNanos.get() + System.nanoTime()); - } - - /** - * Marks the entry as scheduled. - */ - synchronized void schedule(Future entryFuture) { - this.entryFuture = entryFuture; - scheduled = true; - } - } -} diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringValue.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringValue.java deleted file mode 100644 index f19d8fb..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/ExpiringValue.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap; - -import java.util.concurrent.TimeUnit; - -/** - * A value which should be stored in an {@link ExpiringMap} with optional control over its expiration. - * - * @param the type of value being stored - */ -public final class ExpiringValue { - private static final long UNSET_DURATION = -1L; - private final V value; - private final ExpirationPolicy expirationPolicy; - private final long duration; - private final TimeUnit timeUnit; - - /** - * Creates an ExpiringValue to be stored in an {@link ExpiringMap}. The map's default values for - * {@link ExpirationPolicy expiration policy} and {@link ExpiringMap#getExpiration()} expiration} will be used. - * - * @param value the value to store - * @see ExpiringMap#put(Object, Object) - */ - public ExpiringValue(V value) { - this(value, UNSET_DURATION, null, null); - } - - /** - * Creates an ExpiringValue to be stored in an {@link ExpiringMap}. The map's default - * {@link ExpiringMap#getExpiration()} expiration} will be used. - * - * @param value the value to store - * @param expirationPolicy the expiration policy for the value - * @see ExpiringMap#put(Object, Object, ExpirationPolicy) - */ - public ExpiringValue(V value, ExpirationPolicy expirationPolicy) { - this(value, UNSET_DURATION, null, expirationPolicy); - } - - /** - * Creates an ExpiringValue to be stored in an {@link ExpiringMap}. The map's default {@link ExpirationPolicy - * expiration policy} will be used. - * - * @param value the value to store - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @throws NullPointerException on null timeUnit - * @see ExpiringMap#put(Object, Object, long, TimeUnit) - */ - public ExpiringValue(V value, long duration, TimeUnit timeUnit) { - this(value, duration, timeUnit, null); - if (timeUnit == null) { - throw new NullPointerException(); - } - } - - /** - * Creates an ExpiringValue to be stored in an {@link ExpiringMap}. - * - * @param value the value to store - * @param duration the length of time after an entry is created that it should be removed - * @param timeUnit the unit that {@code duration} is expressed in - * @param expirationPolicy the expiration policy for the value - * @throws NullPointerException on null timeUnit - * @see ExpiringMap#put(Object, Object, ExpirationPolicy, long, TimeUnit) - */ - public ExpiringValue(V value, ExpirationPolicy expirationPolicy, long duration, TimeUnit timeUnit) { - this(value, duration, timeUnit, expirationPolicy); - if (timeUnit == null) { - throw new NullPointerException(); - } - } - - private ExpiringValue(V value, long duration, TimeUnit timeUnit, ExpirationPolicy expirationPolicy) { - this.value = value; - this.expirationPolicy = expirationPolicy; - this.duration = duration; - this.timeUnit = timeUnit; - } - - public V getValue() { - return value; - } - - public ExpirationPolicy getExpirationPolicy() { - return expirationPolicy; - } - - public long getDuration() { - return duration; - } - - public TimeUnit getTimeUnit() { - return timeUnit; - } - - @Override - public int hashCode() { - return value != null ? value.hashCode() : 0; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ExpiringValue that = (ExpiringValue) o; - return !(value != null ? !value.equals(that.value) : that.value != null) - && expirationPolicy == that.expirationPolicy && duration == that.duration && timeUnit == that.timeUnit; - - } - - @Override - public String toString() { - return "ExpiringValue{" + "value=" + value + ", expirationPolicy=" + expirationPolicy + ", duration=" + duration - + ", timeUnit=" + timeUnit + '}'; - } -} diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/Assert.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/Assert.java deleted file mode 100644 index 6125a3b..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/Assert.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap.internal; - -import java.util.NoSuchElementException; - -/** - * @author Jonathan Halterman - */ -public final class Assert { - private Assert() { - } - - public static T notNull(T reference, String parameterName) { - if (reference == null) - throw new NullPointerException(parameterName + " cannot be null"); - return reference; - } - - public static void operation(boolean condition, String message) { - if (!condition) - throw new UnsupportedOperationException(message); - } - - public static void state(boolean expression, String errorMessageFormat, Object... args) { - if (!expression) - throw new IllegalStateException(String.format(errorMessageFormat, args)); - } - - public static void element(Object element, Object key) { - if (element == null) - throw new NoSuchElementException(key.toString()); - } -} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/NamedThreadFactory.java b/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/NamedThreadFactory.java deleted file mode 100644 index 196b27b..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/expiringmap/internal/NamedThreadFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.io.yutian.elementoriginlib.expiringmap.internal; - -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Named thread factory. - */ -public class NamedThreadFactory implements ThreadFactory { - private final AtomicInteger threadNumber = new AtomicInteger(1); - private final String nameFormat; - - /** - * Creates a thread factory that names threads according to the {@code nameFormat} by supplying a - * single argument to the format representing the thread number. - */ - public NamedThreadFactory(String nameFormat) { - this.nameFormat = nameFormat; - } - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, String.format(nameFormat, threadNumber.getAndIncrement())); - thread.setDaemon(true); - return thread; - } -} diff --git a/src/main/java/com/io/yutian/elementoriginlib/gui/DebugGui.java b/src/main/java/com/io/yutian/elementoriginlib/gui/DebugGui.java new file mode 100644 index 0000000..dd9a6a4 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/gui/DebugGui.java @@ -0,0 +1,55 @@ +package com.io.yutian.elementoriginlib.gui; + +import com.io.yutian.elementoriginlib.util.ItemStackBuilder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class DebugGui extends Gui { + + private Set cachedSlots = new HashSet<>(); + + public DebugGui(Player player, int size) { + super(player, Component.text("Debug Gui"), size); + } + + @Override + public void init() { + } + + @Override + public void close(InventoryCloseEvent event) { + StringBuilder sb = new StringBuilder(); + int i = 0; + List list = new ArrayList<>(cachedSlots); + list.sort(Integer::compareTo); + for (int slot : list) { + sb.append(slot); + if (i < list.size() - 1) { + sb.append(", "); + } + i++; + } + player.sendMessage(Component.text("§b["+sb.toString()+"]").clickEvent(ClickEvent.clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, sb.toString())).hoverEvent(Component.text("§f§l[点击复制]"))); + } + + @Override + public void handler(Player player, int slot, InventoryClickEvent event) { + if (slot < inventory.getSize()) { + if (!cachedSlots.contains(slot)) { + cachedSlots.add(slot); + inventory.setItem(slot, new ItemStackBuilder(Material.SLIME_BALL).setDisplayName(" ").setDisplayName("§a"+slot).build()); + } + event.setCancelled(true); + } + } + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/gui/Gui.java b/src/main/java/com/io/yutian/elementoriginlib/gui/Gui.java index 326728e..e892cc0 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/gui/Gui.java +++ b/src/main/java/com/io/yutian/elementoriginlib/gui/Gui.java @@ -13,7 +13,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import java.util.HashMap; import java.util.Map; -public class Gui extends IGui { +public abstract class Gui extends IGui { public Map buttons = new HashMap<>(); @@ -21,10 +21,6 @@ public class Gui extends IGui { super(player, title, size); } - @Override - public void init() { - } - @Override public void handler(Player player, int slot, InventoryClickEvent event) { if (buttons.containsKey(slot)) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/gui/IGui.java b/src/main/java/com/io/yutian/elementoriginlib/gui/IGui.java index 8e5e904..93b4c13 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/gui/IGui.java +++ b/src/main/java/com/io/yutian/elementoriginlib/gui/IGui.java @@ -38,6 +38,7 @@ public abstract class IGui implements InventoryHolder { } public void open() { + init(); player.openInventory(inventory); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/gui/PageGui.java b/src/main/java/com/io/yutian/elementoriginlib/gui/PageGui.java index e63b1cb..d0e2ddf 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/gui/PageGui.java +++ b/src/main/java/com/io/yutian/elementoriginlib/gui/PageGui.java @@ -13,7 +13,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import java.util.HashMap; import java.util.Map; -public class PageGui extends IGui { +public abstract class PageGui extends IGui { private int page = 1; private int maxPage; @@ -25,6 +25,13 @@ public class PageGui extends IGui { this.maxPage = maxPage; } + @Override + public void open() { + init(); + initButton(); + player.openInventory(inventory); + } + public void initButton() { initButton(this.page); } @@ -63,10 +70,6 @@ public class PageGui extends IGui { } } - @Override - public void init() { - } - @Override public void handler(Player player, int slot, InventoryClickEvent event) { if (pages.containsKey(page)) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItem.java b/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItem.java index 16ec1cb..132cc07 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItem.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItem.java @@ -24,16 +24,40 @@ public class TagStatItem { this.builder = new TagStatItemStackBuilder(this); } + public void setStatData(@NotNull Class statClass, @NotNull StatData data) { + ItemStat itemStat = ItemStats.getItemStat(statClass); + if (itemStat == null) { + return; + } + setStatData(itemStat, data); + } + public void setStatData(@NotNull ItemStat stat, @NotNull StatData data) { this.stats.put(stat, data); this.itemStack = builder().build(); } + public void removeStatData(@NotNull Class statClass) { + ItemStat itemStat = ItemStats.getItemStat(statClass); + if (itemStat == null) { + return; + } + removeStatData(itemStat); + } + public void removeStatData(@NotNull ItemStat stat) { this.stats.remove(stat); this.itemStack = builder().build(); } + public S getStatData(@NotNull Class> statClass) { + ItemStat itemStat = ItemStats.getItemStat(statClass); + if (itemStat == null) { + return null; + } + return (S) getStatData(itemStat); + } + public S getStatData(@NotNull ItemStat stat) { if (!hasStatData(stat)) { stat.load(this); @@ -41,6 +65,14 @@ public class TagStatItem { return (S) this.stats.get(stat); } + public boolean hasStatData(@NotNull Class statClass) { + ItemStat itemStat = ItemStats.getItemStat(statClass); + if (itemStat == null) { + return false; + } + return hasStatData(itemStat); + } + public boolean hasStatData(@NotNull ItemStat stat) { if (!this.stats.containsKey(stat)) { try { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItemStackBuilder.java b/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItemStackBuilder.java index 6beefad..5b88f6f 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItemStackBuilder.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/TagStatItemStackBuilder.java @@ -2,13 +2,9 @@ package com.io.yutian.elementoriginlib.item; import com.io.yutian.elementoriginlib.tag.ItemProxy; import com.io.yutian.elementoriginlib.tag.TagCompound; -import net.kyori.adventure.text.Component; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; public class TagStatItemStackBuilder { @@ -16,14 +12,12 @@ public class TagStatItemStackBuilder { private final TagStatItem tagStatItem; protected ItemStack itemStack; - protected ItemMeta itemMeta; protected ItemProxy itemProxy; public TagStatItemStackBuilder(TagStatItem tagStatItem) { this.tagStatItem = tagStatItem; this.itemStack = tagStatItem.getItemStack(); - this.itemMeta = itemStack.getItemMeta(); this.itemProxy = tagStatItem.getItemProxy(); } @@ -37,20 +31,10 @@ public class TagStatItemStackBuilder { return itemStack; } - @NotNull - public ItemMeta getItemMeta() { - return itemMeta; - } - private void buildCompounds() { tagStatItem.getStats().forEach((stat, statData) -> stat.whenApplied(this, statData)); itemStack = itemProxy.getItemStack(); - itemMeta = itemStack.getItemMeta(); tagStatItem.getStats().forEach((stat, statData) -> stat.applyMeta(this, statData)); - List lores = itemMeta.hasLore() ? itemMeta.lore() : new ArrayList<>(); - tagStatItem.getStats().forEach((stat, statData) -> stat.whenApplyLore(this, statData, lores)); - itemMeta.lore(lores); - itemStack.setItemMeta(itemMeta); } @NotNull @@ -58,6 +42,10 @@ public class TagStatItemStackBuilder { return itemProxy; } + public ItemStack getItemStack() { + return itemStack; + } + public TagStatItem getOriginItem() { return tagStatItem; } diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStat.java index 849edc6..25d329f 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStat.java @@ -1,14 +1,11 @@ package com.io.yutian.elementoriginlib.item.stat; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItem; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.tag.ItemProxy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - public abstract class ItemStat { @NotNull @@ -23,14 +20,9 @@ public abstract class ItemStat { public abstract void whenApplied(@NotNull TagStatItemStackBuilder itemStackBuilder, @NotNull S statData); - public abstract S loadAsObject(@NotNull Object value) throws ItemStatDataLoadException; - public void applyMeta(@NotNull TagStatItemStackBuilder itemStackBuilder, @NotNull S statData) { } - public void whenApplyLore(@NotNull TagStatItemStackBuilder itemStackBuilder, @NotNull S statData, @NotNull List lores) { - } - @Nullable public abstract S getLoadedTag(@NotNull ItemProxy itemProxy); diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStats.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStats.java index 564447b..9211e95 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStats.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/ItemStats.java @@ -4,10 +4,7 @@ import com.io.yutian.elementoriginlib.item.stat.list.IdStat; import com.io.yutian.elementoriginlib.logger.Logger; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class ItemStats { @@ -21,13 +18,28 @@ public class ItemStats { public static void register(ItemStat itemStat) { Class clazz = itemStat.getClass(); - if (itemStats.containsKey(itemStat.getId())) { - LOGGER.warn("ItemStat "+clazz.getName()+" 已存在"); - return; + if (isRegistered(itemStat)) { + LOGGER.warn("ItemStat "+clazz.getName()+" 已注册,将覆盖原有注册"); + Set> removeSet = new HashSet<>(); + for (Class clazz1 : itemStats.keySet()) { + if (clazz1.getName().equalsIgnoreCase(itemStat.getClass().getName())) { + removeSet.add(clazz1); + } + } + removeSet.forEach((aClass -> itemStats.remove(aClass))); } itemStats.put(clazz, itemStat); } + private static boolean isRegistered(ItemStat itemStat) { + for (Class clazz : itemStats.keySet()) { + if (clazz.getName().equalsIgnoreCase(itemStat.getClass().getName())) { + return true; + } + } + return itemStats.containsKey(itemStat.getClass()); + } + public static void unregister(ItemStat itemStat) { itemStats.remove(itemStat.getId()); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/BooleanStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/BooleanStat.java index e2491f0..50ada63 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/BooleanStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/BooleanStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.BooleanData; @@ -9,8 +8,6 @@ import com.io.yutian.elementoriginlib.tag.ItemProxy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - public abstract class BooleanStat extends ItemStat { public BooleanStat(@NotNull String id, @NotNull String path) { @@ -23,32 +20,6 @@ public abstract class BooleanStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.putByte(getTagPath(), (byte) (statData.getValue() ? 1 : 0))); } - @Override - public BooleanData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof Boolean bool) { - return new BooleanData(bool); - } else if (value instanceof String arg) { - boolean parsed = Boolean.parseBoolean(arg); - if (!arg.equalsIgnoreCase("true") && !arg.equalsIgnoreCase("false")) { - throw new ItemStatDataLoadException("Invalid boolean string: " + arg); - } - return new BooleanData(parsed); - } else if (value instanceof Byte b) { - return new BooleanData(b == 1); - } else if (value instanceof Integer i) { - return new BooleanData(i == 1); - } else { - throw new ItemStatDataLoadException("Invalid boolean value: " + value); - } - } catch (ClassCastException e) { - throw new ItemStatDataLoadException("Invalid type: " + value.getClass(), e); - } - } - @Nullable @Override public BooleanData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleListStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleListStat.java index 468c92f..ffca6f5 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleListStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleListStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.DoubleListData; @@ -11,9 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Objects; public abstract class DoubleListStat extends ItemStat { @@ -29,58 +26,6 @@ public abstract class DoubleListStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.put(getTagPath(), tagList)); } - @Override - public DoubleListData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof List list) { - List doubleList = new ArrayList<>(); - for (Object item : list) { - if (item instanceof Double d) { - doubleList.add(d); - } else if (item instanceof String str) { - // 如果是 String,尝试将其解析为 Double - try { - doubleList.add(Double.parseDouble(str)); - } catch (NumberFormatException e) { - throw new ItemStatDataLoadException("Invalid string value for Double: " + str, e); - } - } else { - throw new ItemStatDataLoadException("List contains non-double element: " + item); - } - } - return new DoubleListData(doubleList); - } else if (value instanceof double[] array) { - List doubleList = new ArrayList<>(); - for (double d : array) { - doubleList.add(d); - } - return new DoubleListData(doubleList); - } else if (value instanceof Double[] array) { - List doubleList = new ArrayList<>(Arrays.asList(array)); - return new DoubleListData(doubleList); - } else if (value instanceof String str) { - List doubleList = new ArrayList<>(); - String[] items = str.split(","); - for (String item : items) { - try { - doubleList.add(Double.parseDouble(item.trim())); - } catch (NumberFormatException e) { - throw new ItemStatDataLoadException("Invalid number format in string: " + item, e); - } - } - return new DoubleListData(doubleList); - } else { - throw new ItemStatDataLoadException("Invalid value type for DoubleListData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - - @Nullable @Override public DoubleListData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleStat.java index c61ea66..4963532 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/DoubleStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.DoubleData; @@ -9,8 +8,6 @@ import com.io.yutian.elementoriginlib.tag.ItemProxy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - public abstract class DoubleStat extends ItemStat { public DoubleStat(@NotNull String id, @NotNull String path) { @@ -23,34 +20,6 @@ public abstract class DoubleStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.putDouble(getTagPath(), statData.getValue())); } - @Override - public DoubleData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof Double d) { - return new DoubleData(d); - } else if (value instanceof String arg) { - try { - return new DoubleData(Double.parseDouble(arg)); - } catch (NumberFormatException e) { - throw new ItemStatDataLoadException("Invalid double string: " + arg, e); - } - } else if (value instanceof Byte b) { - return new DoubleData((double) b); - } else if (value instanceof Integer i) { - return new DoubleData((double) i); - } else if (value instanceof Long l) { - return new DoubleData((double) l); - } else { - throw new ItemStatDataLoadException("Invalid double value: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Nullable @Override public DoubleData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumListStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumListStat.java index 7148e8b..4fd143e 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumListStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumListStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.EnumListData; @@ -12,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Objects; public abstract class EnumListStat extends ItemStat> { @@ -32,55 +30,6 @@ public abstract class EnumListStat extends ItemStat tagCompound1.put(getTagPath(), tagList)); } - @Override - public EnumListData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof List list) { - List enumList = new ArrayList<>(); - for (Object item : list) { - if (item instanceof String str) { - try { - E enumConstant = (E) Enum.valueOf(clazz, str); - enumList.add(enumConstant); - } catch (IllegalArgumentException e) { - throw new ItemStatDataLoadException("Invalid enum string: " + str, e); - } - } else if (item instanceof Integer i) { - E[] enumConstants = clazz.getEnumConstants(); - if (i < 0 || i >= enumConstants.length) { - throw new ItemStatDataLoadException("Invalid enum ordinal: " + i); - } - enumList.add(enumConstants[i]); - } else if (item instanceof Enum enumItem) { - enumList.add((E) enumItem); - } else { - throw new ItemStatDataLoadException("Invalid list element type: " + item); - } - } - return new EnumListData<>(enumList); - } else if (value instanceof String str) { - List enumList = new ArrayList<>(); - String[] enumNames = str.split(","); - for (String enumName : enumNames) { - try { - E enumConstant = (E) Enum.valueOf(clazz, enumName.trim()); - enumList.add(enumConstant); - } catch (IllegalArgumentException e) { - throw new ItemStatDataLoadException("Invalid enum string: " + enumName, e); - } - } - return new EnumListData<>(enumList); - } else { - throw new ItemStatDataLoadException("Invalid value type for EnumListData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Override public @Nullable EnumListData getLoadedTag(@NotNull ItemProxy itemProxy) { if (!itemProxy.has(getTagPath(), TagList.TYPE_ID)) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumStat.java index 07630f9..811b775 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/EnumStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.EnumData; @@ -9,8 +8,6 @@ import com.io.yutian.elementoriginlib.tag.TagString; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - public abstract class EnumStat extends ItemStat> { private Class clazz; @@ -26,35 +23,6 @@ public abstract class EnumStat extends ItemStat> { itemProxy.editTag(tagCompound1 -> tagCompound1.putString(getTagPath(), statData.getValue().name())); } - @Override - public EnumData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof Enum enumValue) { - return new EnumData<>((E) enumValue); - } else if (value instanceof String str) { - try { - E enumConstant = (E) Enum.valueOf(clazz, str); - return new EnumData<>(enumConstant); - } catch (IllegalArgumentException e) { - throw new ItemStatDataLoadException("Invalid enum string: " + str, e); - } - } else if (value instanceof Integer i) { - E[] enumConstants = clazz.getEnumConstants(); - if (i < 0 || i >= enumConstants.length) { - throw new ItemStatDataLoadException("Invalid enum ordinal: " + i); - } - return new EnumData<>(enumConstants[i]); - } else { - throw new ItemStatDataLoadException("Invalid value type for EnumData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Override public @Nullable EnumData getLoadedTag(@NotNull ItemProxy itemProxy) { return new EnumData(Enum.valueOf(clazz, ((TagString) itemProxy.get(getTagPath())).getString())); diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntListStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntListStat.java index cb27346..4146a47 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntListStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntListStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.IntListData; @@ -12,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Objects; public abstract class IntListStat extends ItemStat { @@ -28,47 +26,6 @@ public abstract class IntListStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.put(getTagPath(), tagList)); } - @Override - public IntListData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof List list) { - List intList = new ArrayList<>(); - for (Object item : list) { - if (item instanceof Integer i) { - intList.add(i); - } else { - throw new ItemStatDataLoadException("List contains non-integer element: " + item); - } - } - return new IntListData(intList); - } else if (value instanceof int[] array) { - List intList = new ArrayList<>(); - for (int i : array) { - intList.add(i); - } - return new IntListData(intList); - } else if (value instanceof String str) { - try { - List intList = new ArrayList<>(); - String[] items = str.split(","); - for (String item : items) { - intList.add(Integer.parseInt(item.trim())); - } - return new IntListData(intList); - } catch (NumberFormatException e) { - throw new ItemStatDataLoadException("Invalid integer string: " + str, e); - } - } else { - throw new ItemStatDataLoadException("Invalid value type for IntListData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Nullable @Override public IntListData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntStat.java index 24aba83..a90136b 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/IntStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.IntData; @@ -9,8 +8,6 @@ import com.io.yutian.elementoriginlib.tag.ItemProxy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - public abstract class IntStat extends ItemStat { public IntStat(@NotNull String id, @NotNull String path) { @@ -23,48 +20,6 @@ public abstract class IntStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.putInt(getTagPath(), statData.getInt())); } - @Override - public IntData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof Integer i) { - return new IntData(i); - } else if (value instanceof String arg) { - try { - return new IntData(Integer.parseInt(arg)); - } catch (NumberFormatException e) { - throw new ItemStatDataLoadException("Invalid integer string: " + arg, e); - } - } else if (value instanceof Byte b) { - return new IntData((int) b); - } else if (value instanceof Long l) { - if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { - throw new ItemStatDataLoadException("Long value out of range for integer: " + l); - } - return new IntData(l.intValue()); - } else if (value instanceof Double d) { - if (d < Integer.MIN_VALUE || d > Integer.MAX_VALUE) { - throw new ItemStatDataLoadException("Double value out of range for integer: " + d); - } - return new IntData((int) d.doubleValue()); - } else if (value instanceof Float f) { - if (f < Integer.MIN_VALUE || f > Integer.MAX_VALUE) { - throw new ItemStatDataLoadException("Float value out of range for integer: " + f); - } - return new IntData((int) f.floatValue()); - } else { - throw new ItemStatDataLoadException("Invalid integer value: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - - - - @Nullable @Override public IntData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/MapStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/MapStat.java index 54c5c36..ddd20cf 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/MapStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/MapStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.MapData; @@ -10,7 +9,6 @@ import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; -import java.util.Objects; public class MapStat extends ItemStat { @@ -28,30 +26,6 @@ public class MapStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.put(getTagPath(), tagCompound)); } - @Override - public MapData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof Map map) { - Map resultMap = new HashMap<>(); - for (Map.Entry entry : map.entrySet()) { - if (entry.getKey() instanceof String key) { - resultMap.put(key, entry.getValue()); - } else { - throw new ItemStatDataLoadException("Map key is not a String: " + entry.getKey()); - } - } - return new MapData(resultMap); - } else { - throw new ItemStatDataLoadException("Invalid value type for MapData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Override public @Nullable MapData getLoadedTag(@NotNull ItemProxy itemProxy) { if (!itemProxy.has(getTagPath(), TagCompound.TYPE_ID)) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringListStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringListStat.java index f8149c1..eea73f4 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringListStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringListStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.StringListData; @@ -11,9 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Objects; public abstract class StringListStat extends ItemStat { @@ -29,38 +26,6 @@ public abstract class StringListStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.put(getTagPath(), tagList)); } - @Override - public StringListData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof List list) { - List stringList = new ArrayList<>(); - for (Object item : list) { - if (item instanceof String str) { - stringList.add(str); - } else { - throw new ItemStatDataLoadException("List contains non-string element: " + item); - } - } - return new StringListData(stringList); - } else if (value instanceof String[] array) { - // 处理 String[] 类型 - return new StringListData(Arrays.asList(array)); - } else if (value instanceof String str) { - List stringList = new ArrayList<>(); - stringList.add(str); - return new StringListData(stringList); - } else { - throw new ItemStatDataLoadException("Invalid value type for StringListData: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - - @Nullable @Override public StringListData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringStat.java index bf3e739..1f8eaff 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/StringStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.StringData; @@ -9,8 +8,6 @@ import com.io.yutian.elementoriginlib.tag.TagString; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - public abstract class StringStat extends ItemStat { public StringStat(@NotNull String id, @NotNull String path) { @@ -23,14 +20,6 @@ public abstract class StringStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.putString(getTagPath(), statData.getString())); } - @Override - public StringData loadAsObject(@NotNull Object value) { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - return new StringData(value.toString()); - } - @Nullable @Override public StringData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/UUIDStat.java b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/UUIDStat.java index a3c5954..2e7a3b8 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/UUIDStat.java +++ b/src/main/java/com/io/yutian/elementoriginlib/item/stat/type/UUIDStat.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.item.stat.type; -import com.io.yutian.elementoriginlib.exception.itemstat.ItemStatDataLoadException; import com.io.yutian.elementoriginlib.item.TagStatItemStackBuilder; import com.io.yutian.elementoriginlib.item.stat.ItemStat; import com.io.yutian.elementoriginlib.item.stat.data.UUIDData; @@ -9,7 +8,6 @@ import com.io.yutian.elementoriginlib.tag.TagString; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; import java.util.UUID; public abstract class UUIDStat extends ItemStat { @@ -24,28 +22,6 @@ public abstract class UUIDStat extends ItemStat { itemProxy.editTag(tagCompound1 -> tagCompound1.putString(getTagPath(), statData.getUUID().toString())); } - @Override - public UUIDData loadAsObject(@NotNull Object value) throws ItemStatDataLoadException { - if (Objects.isNull(value)) { - throw new ItemStatDataLoadException("Invalid value: null"); - } - try { - if (value instanceof UUID uuid) { - return new UUIDData(uuid); - } else if (value instanceof String str) { - try { - return new UUIDData(UUID.fromString(str)); - } catch (IllegalArgumentException e) { - throw new ItemStatDataLoadException("Invalid UUID string: " + str, e); - } - } else { - throw new ItemStatDataLoadException("Invalid UUID value: " + value); - } - } catch (Exception e) { - throw new ItemStatDataLoadException(e); - } - } - @Nullable @Override public UUIDData getLoadedTag(@NotNull ItemProxy itemProxy) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/lang/Lang.java b/src/main/java/com/io/yutian/elementoriginlib/lang/Lang.java index acace9d..b875e7f 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/lang/Lang.java +++ b/src/main/java/com/io/yutian/elementoriginlib/lang/Lang.java @@ -15,10 +15,12 @@ public class Lang { private static Map langMap = new HashMap<>(); public static String get(String key) { + key = key.toLowerCase(); return langMap.getOrDefault(key, "§o"+key); } public static String get(String key, Object... args) { + key = key.toLowerCase(); String s = langMap.getOrDefault(key, "§o"+key); for (int i = 0; i < args.length; i++) { s = s.replace("$"+i, String.valueOf(args[i])); @@ -27,6 +29,7 @@ public class Lang { } public static Optional getOptional(String key) { + key = key.toLowerCase(); return Optional.ofNullable(langMap.get(key)); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/mongodb/Collection.java b/src/main/java/com/io/yutian/elementoriginlib/mongodb/Collection.java new file mode 100644 index 0000000..6dc67c3 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/mongodb/Collection.java @@ -0,0 +1,14 @@ +package com.io.yutian.elementoriginlib.mongodb; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Collection { + + String value() default ""; + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoEntity.java b/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoEntity.java new file mode 100644 index 0000000..051bc68 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoEntity.java @@ -0,0 +1,9 @@ +package com.io.yutian.elementoriginlib.mongodb; + +import java.util.UUID; + +public interface MongoEntity { + + UUID getUUID(); + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoSupport.java b/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoSupport.java new file mode 100644 index 0000000..a03906d --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/mongodb/MongoSupport.java @@ -0,0 +1,722 @@ +package com.io.yutian.elementoriginlib.mongodb; + +import com.io.yutian.elementoriginlib.serialize.SerializeHelper; +import com.mongodb.BasicDBObject; +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.*; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import org.bson.Document; +import org.bson.UuidRepresentation; +import org.bson.codecs.UuidCodec; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class MongoSupport { + private final static Logger LOG = LoggerFactory.getLogger(MongoSupport.class); + public final static String ID = "_id"; + private final static Map, String> classCollectionName = new ConcurrentHashMap<>(); + private final static Map> collections = new ConcurrentHashMap<>(); + + private static final CodecRegistry CODEC_REGISTRY = CodecRegistries.fromRegistries( + CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)), + MongoClientSettings.getDefaultCodecRegistry() + ); + + protected MongoDatabase mongoDatabase; + + public MongoSupport(String host, int port, String username, String password, String dbname) { + String connectionString = String.format( + "mongodb://%s:%s@%s:%d/?authSource=admin", + URLEncoder.encode(username, StandardCharsets.UTF_8), + URLEncoder.encode(password, StandardCharsets.UTF_8), + host, port + ); + MongoClientSettings settings = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(connectionString)) + .codecRegistry(CODEC_REGISTRY) + .uuidRepresentation(UuidRepresentation.STANDARD) + .build(); + MongoClient client = MongoClients.create(settings); + mongoDatabase = client.getDatabase(dbname); + if (mongoDatabase == null) { + throw new IllegalArgumentException("获取数据库实例失败:" + dbname); + } + } + + public MongoSupport(MongoClient mongo, String dbname) { + mongoDatabase = mongo.getDatabase(dbname); + if (mongoDatabase == null) { + throw new IllegalArgumentException("获取数据库实例失败:" + dbname); + } + } + + public MongoDatabase getMongoDatabase() { + return mongoDatabase; + } + + public List getCollectionNames() { + List list = new ArrayList<>(); + MongoCursor cursor = mongoDatabase.listCollectionNames().iterator(); + while (cursor.hasNext()) { + list.add(cursor.next()); + } + cursor.close(); + return list; + } + + public MongoCollection getCollection(String collectionName) { + MongoCollection coll = collections.get(collectionName); + if (coll == null) { + coll = mongoDatabase.getCollection(collectionName); + collections.put(collectionName, coll); + } + return coll; + } + + public List findAll(String collectionName) { + return find(collectionName, null, null, 0, 0); + } + + public List findAll(String collectionName, Bson orderBy) { + return find(collectionName, null, orderBy, 0, 0); + } + + public List findAll(String collectionName, Bson orderBy, int limit) { + return find(collectionName, null, orderBy, limit, 0); + } + + public List findAll(String collectionName, int limit) { + return find(collectionName, null, null, limit, 0); + } + + public List findAll(String collectionName, int limit, int skip) { + return find(collectionName, null, null, limit, skip); + } + + public List findAll(String collectionName, Bson orderBy, int limit, int skip) { + return find(collectionName, null, orderBy, limit, skip); + } + + private String getCollectionName(Class clazz) { + if (classCollectionName.containsKey(clazz)) { + return classCollectionName.get(clazz); + } + String collectionName = clazz.getSimpleName(); + Collection collection = clazz.getAnnotation(Collection.class); + if (collection != null && collection.value() != null && !collection.value().isEmpty()) { + collectionName = collection.value(); + } + return classCollectionName.put(clazz, collectionName); + } + + public List findAll(Class clazz) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, null, null, 0, 0); + } + + public List findAll(Class clazz, String collectionName) { + return find(clazz, collectionName, null, null, 0, 0); + } + + public List findAll(Class clazz, String collectionName, Bson orderBy) { + return find(clazz, collectionName, null, orderBy, 0, 0); + } + + public List findAll(Class clazz, String collectionName, Bson orderBy, int limit) { + return find(clazz, collectionName, null, orderBy, limit, 0); + } + + public List findAll(Class clazz, String collectionName, int limit) { + return find(clazz, collectionName, null, null, limit, 0); + } + + public List findAll(Class clazz, String collectionName, int limit, int skip) { + return find(clazz, collectionName, null, null, limit, skip); + } + + public List findAll(Class clazz, String collectionName, Bson orderBy, int limit, int skip) { + return find(clazz, collectionName, null, orderBy, limit, skip); + } + + public List find(String collectionName, Bson filter) { + return find(collectionName, filter, null, 0, 0); + } + + public List find(String collectionName, Bson filter, Bson orderBy) { + return find(collectionName, filter, orderBy, 0, 0); + } + + public List find(String collectionName, Bson filter, Bson orderBy, int limit) { + return find(collectionName, filter, orderBy, 0, 0); + } + + public List find(String collectionName, Bson filter, int limit) { + return find(collectionName, filter, null, 0, 0); + } + + public List find(String collectionName, Bson filter, int limit, int skip) { + return find(collectionName, filter, null, 0, 0); + } + + public List find(String collectionName, Bson filter, Bson orderBy, int limit, int skip) { + FindIterable find = null; + if (filter == null) { + find = getCollection(collectionName).find(); + } else { + find = getCollection(collectionName).find(filter); + } + if (orderBy != null) { + find.sort(orderBy); + } + if (skip > 0) { + find.skip(skip); + } + if (limit > 0) { + find.limit(limit); + } + MongoCursor cursor = find.iterator(); + List list = new ArrayList<>(); + try { + while (cursor.hasNext()) { + list.add(cursor.next()); + } + } finally { + cursor.close(); + } + return list; + } + + public List find(Class clazz, Bson filter) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, filter, null, 0, 0); + } + + public List find(Class clazz, Bson filter, Bson orderBy) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, filter, orderBy, 0, 0); + } + + public List find(Class clazz, Bson filter, Bson orderBy, int limit) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, filter, orderBy, limit, 0); + } + + public List find(Class clazz, Bson filter, int limit) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, filter, null, limit, 0); + } + + public List find(Class clazz, Bson filter, int limit, int skip) { + String collectionName = getCollectionName(clazz); + return find(clazz, collectionName, filter, null, limit, skip); + } + + public List find(Class clazz, String collectionName, Bson filter) { + return find(clazz, collectionName, filter, null, 0, 0); + } + + public List find(Class clazz, String collectionName, Bson filter, Bson orderBy) { + return find(clazz, collectionName, filter, orderBy, 0, 0); + } + + public List find(Class clazz, String collectionName, Bson filter, Bson orderBy, int limit) { + return find(clazz, collectionName, filter, orderBy, limit, 0); + } + + public List find(Class clazz, String collectionName, Bson filter, int limit) { + return find(clazz, collectionName, filter, null, limit, 0); + } + + public List find(Class clazz, String collectionName, Bson filter, int limit, int skip) { + return find(clazz, collectionName, filter, null, limit, skip); + } + + public List find(Class clazz, String collectionName, Bson filter, Bson orderBy, int limit, int skip) { + FindIterable find = null; + if (filter == null) { + find = getCollection(collectionName).find(); + } else { + find = getCollection(collectionName).find(filter); + } + if (orderBy != null) { + find.sort(orderBy); + } + if (skip > 0) { + find.skip(skip); + } + if (limit > 0) { + find.limit(limit); + } + MongoCursor cursor = find.iterator(); + List list = new ArrayList(); + try { + while (cursor.hasNext()) { + Document doc = cursor.next(); + ObjectId id = doc.getObjectId(ID); + if (id != null) { + doc.put(ID, id.toHexString()); + } + String json = doc.toJson(); + list.add(SerializeHelper.deserialize(json, clazz)); + } + } finally { + cursor.close(); + } + return list; + } + + public List distinct(String collectionName, String fieldName, Bson filter) { + DistinctIterable find = null; + if (filter == null) { + find = getCollection(collectionName).distinct(fieldName, Document.class); + } else { + find = getCollection(collectionName).distinct(fieldName, filter, Document.class); + } + MongoCursor cursor = find.iterator(); + List list = new ArrayList<>(); + try { + while (cursor.hasNext()) { + list.add(cursor.next()); + } + } finally { + cursor.close(); + } + return list; + } + + public Document findOne(String collectionName, Bson filter) { + MongoCursor cursor = getCollection(collectionName).find(filter).iterator(); + try { + if (cursor.hasNext()) { + return cursor.next(); + } + } finally { + cursor.close(); + } + return null; + } + + public T findOne(Class clazz, Bson filter) { + String collectionName = getCollectionName(clazz); + return findOne(clazz, collectionName, filter); + } + + public T findOne(Class clazz, String collectionName, Bson filter) { + MongoCursor cursor = getCollection(collectionName).find(filter).iterator(); + try { + if (cursor.hasNext()) { + Document doc = cursor.next(); + ObjectId id = doc.getObjectId(ID); + if (id != null) { + doc.put(ID, id.toHexString()); + } + String json = doc.toJson(); + return SerializeHelper.deserialize(json, clazz); + } + } finally { + cursor.close(); + } + return null; + } + + public T findById(Class clazz, UUID id) { + String collectionName = getCollectionName(clazz); + return findById(clazz, collectionName, id); + } + + public T findById(Class clazz, String collectionName, UUID id) { + Bson filter = Filters.eq(ID, id); + MongoCursor cursor = getCollection(collectionName).find(filter).iterator(); + try { + if (cursor.hasNext()) { + Document doc = cursor.next(); + ObjectId _id = doc.getObjectId(ID); + if (_id != null) { + doc.put(ID, _id.toHexString()); + } + String json = doc.toJson(); + return SerializeHelper.deserialize(json, clazz); + } + } finally { + cursor.close(); + } + return null; + } + + public Document findById(String collectionName, UUID id) { + Bson filter = Filters.eq(ID, id); + MongoCursor cursor = getCollection(collectionName).find(filter).iterator(); + try { + if (cursor.hasNext()) { + return cursor.next(); + } + } finally { + cursor.close(); + } + return null; + } + + public long count(Class clazz) { + String collectionName = getCollectionName(clazz); + return count(collectionName); + } + + public long count(String collectionName) { + return getCollection(collectionName).countDocuments(); + } + + public long count(Class clazz, Bson filter) { + String collectionName = getCollectionName(clazz); + return count(collectionName, filter); + } + + public long count(String collectionName, Bson filter) { + if (filter == null) { + return getCollection(collectionName).countDocuments(); + } + return getCollection(collectionName).countDocuments(filter); + } + + public List count(String collectionName, String[] groupBy) { + return count(collectionName, groupBy, null, 0); + } + + public List count(String collectionName, String[] groupBy, Bson filter) { + return count(collectionName, groupBy, filter, 0); + } + + public List count(String collectionName, String[] groupBy, Bson filter, int limit) { + StringBuilder mapFunction = new StringBuilder("function(){emit("); + int len = groupBy.length; + if (len == 1) { + mapFunction.append("this.").append(groupBy[0]); + } else { + mapFunction.append("{"); + for (int i = 0; i < len; i++) { + if (i > 0) { + mapFunction.append(","); + } + mapFunction.append(groupBy[i]).append(":this.").append(groupBy[i]); + } + mapFunction.append("}"); + } + mapFunction.append(",1"); + mapFunction.append(");}"); + StringBuilder reduceFunction = new StringBuilder("function(key, values){"); + reduceFunction.append("var total = 0;"); + reduceFunction.append("values.forEach(function(val){total += val;});"); + reduceFunction.append("return total;"); + reduceFunction.append("}"); + MapReduceIterable find = getCollection(collectionName).mapReduce(mapFunction.toString(), reduceFunction.toString()); + if (filter != null) { + find.filter(filter); + } + if (limit > 0) { + find.limit(limit); + } + find.jsMode(true); + MongoCursor cursor = find.iterator(); + List list = new ArrayList(); + try { + while (cursor.hasNext()) { + Document doc = cursor.next(); + if (len == 1) { + doc.put(groupBy[0], doc.get("_id")); + } else { + doc.putAll((Document) doc.get("_id")); + } + doc.remove("_id"); + + Object val = doc.get("value"); + if (val instanceof List) { + val = ((List) val).get(0); + } + long count = 0; + if (val instanceof Number) { + count = ((Number)val).longValue(); + } else { + LOG.warn("{} is not a number!!! doc={}", val, doc); + } + doc.remove("value"); + doc.put("count", count); + list.add(doc); + } + } finally { + cursor.close(); + } + return list; + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction) { + return mapReduce(collectionName, mapFunction, reduceFunction, null, null, null, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, Bson filter) { + return mapReduce(collectionName, mapFunction, reduceFunction, null, filter, null, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, Bson filter, Bson orderBy) { + return mapReduce(collectionName, mapFunction, reduceFunction, null, filter, orderBy, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, Bson filter, int limit) { + return mapReduce(collectionName, mapFunction, reduceFunction, null, filter, null, limit); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, String finalizeFunction) { + return mapReduce(collectionName, mapFunction, reduceFunction, finalizeFunction, null, null, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson filter) { + return mapReduce(collectionName, mapFunction, reduceFunction, finalizeFunction, filter, null, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson filter, Bson orderBy) { + return mapReduce(collectionName, mapFunction, reduceFunction, finalizeFunction, filter, orderBy, 0); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson filter, int limit) { + return mapReduce(Document.class, collectionName, mapFunction, reduceFunction, finalizeFunction, filter, null, limit); + } + + public List mapReduce(String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson filter, Bson orderBy, int + limit) { + return mapReduce(Document.class, collectionName, mapFunction, reduceFunction, finalizeFunction, filter, orderBy, limit); + } + + public List mapReduce(Class resultClass, String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson + filter, int limit) { + return mapReduce(resultClass, collectionName, mapFunction, reduceFunction, finalizeFunction, filter, null, limit); + } + + public List mapReduce(Class resultClass, String collectionName, String mapFunction, String reduceFunction, String finalizeFunction, Bson + filter, Bson orderBy, int limit) { + MapReduceIterable find = getCollection(collectionName).mapReduce(mapFunction, reduceFunction, resultClass); + if (filter != null) { + find.filter(filter); + } + if (finalizeFunction != null && !finalizeFunction.isEmpty()) { + find.finalizeFunction(finalizeFunction); + } + if (orderBy != null) { + find.sort(orderBy); + } + if (limit > 0) { + find.limit(limit); + } + find.jsMode(true); + MongoCursor cursor = find.iterator(); + List list = new ArrayList(); + try { + while (cursor.hasNext()) { + list.add(cursor.next()); + } + } finally { + cursor.close(); + } + return list; + } + + public void save(Object entity) { + Class clazz = entity.getClass(); + String collectionName = getCollectionName(clazz); + save(entity, collectionName); + } + + public void save(Object entity, String collectionName) { + if (entity instanceof Document) { + getCollection(collectionName).insertOne((Document) entity); + } else if (entity instanceof Map) { + getCollection(collectionName).insertOne(new Document((Map) entity)); + } else { + String json = toJson(entity); + getCollection(collectionName).insertOne(Document.parse(json)); + } + } + + public void saveAll(List list) { + Class clazz = list.get(0).getClass(); + String collectionName = getCollectionName(clazz); + saveAll(list, collectionName); + } + + public void saveAll(List list, String collectionName) { + List docList = new ArrayList<>(); + for (Object obj : list) { + if (obj instanceof Document) { + docList.add((Document) obj); + } else if (obj instanceof Map) { + docList.add(new Document((Map) obj)); + } else { + String json = toJson(obj); + docList.add(Document.parse(json)); + } + } + getCollection(collectionName).insertMany(docList); + } + + public boolean update(T entity) { + String collectionName = getCollectionName(entity.getClass()); + return update(entity, collectionName); + } + + public boolean update(T entity, String collectionName) { + UUID id = null; + if (entity instanceof MongoEntity mongoEntity) { + + } + return updateById(entity, collectionName, id); + } + + + public long update(T entity, Bson filter) { + String collectionName = getCollectionName(entity.getClass()); + return update(entity, collectionName, filter, false); + } + + public long update(T entity, Bson filter, boolean mutil) { + String collectionName = getCollectionName(entity.getClass()); + return update(entity, collectionName, filter, mutil); + } + + public long update(T entity, String collectionName, Bson filter, boolean mutil) { + String json = toJson(entity); + BasicDBObject update = BasicDBObject.parse(json); + return update(collectionName, filter, new BasicDBObject("$set", update), mutil); + } + + public long update(String collectionName, Bson filter, UpdateEntity updateEntity) { + return update(collectionName, filter, updateEntity, false); + } + + public long update(String collectionName, Bson filter, UpdateEntity updateEntity, boolean mutil) { + BasicDBObject _update = new BasicDBObject(); + if (!updateEntity.inc().isEmpty()) { + _update.append("$inc", updateEntity.inc()); + } + if (!updateEntity.set().isEmpty()) { + _update.append("$set", updateEntity.set()); + } + if (!updateEntity.unset().isEmpty()) { + _update.append("$unset", updateEntity.unset()); + } + return update(collectionName, filter, _update, mutil); + } + + public long update(String collectionName, Bson filter, Bson update, boolean mutil) { + UpdateResult result = null; + if (mutil) { + result = getCollection(collectionName).updateMany(filter, update); + } else { + result = getCollection(collectionName).updateOne(filter, update); + } + if (result.wasAcknowledged()) { + return result.getModifiedCount(); + } + return -1; + } + + public boolean updateById(T entity, UUID id) { + String collectionName = getCollectionName(entity.getClass()); + return updateById(entity, collectionName, id); + } + + public boolean updateById(T entity, String collectionName, UUID id) { + if (id == null) { + throw new IllegalArgumentException("id不能为空"); + } + String json = toJson(entity); + Bson filter = Filters.eq(ID, id); + BasicDBObject update = BasicDBObject.parse(json); + UpdateResult result = getCollection(collectionName).updateOne(filter, new BasicDBObject("$set", update)); + if (result.wasAcknowledged()) { + if (result.getModifiedCount() > 0) { + return true; + } + } else { + return true; + } + return false; + } + + public boolean updateById(String collectionName, UpdateEntity updateEntity, UUID id) { + BasicDBObject _update = new BasicDBObject(); + if (!updateEntity.inc().isEmpty()) { + _update.append("$inc", updateEntity.inc()); + } + if (!updateEntity.set().isEmpty()) { + _update.append("$set", updateEntity.set()); + } + if (!updateEntity.unset().isEmpty()) { + _update.append("$unset", updateEntity.unset()); + } + return updateById(collectionName, _update, id); + } + + public boolean updateById(String collectionName, Bson update, UUID id) { + if (id == null) { + throw new IllegalArgumentException("id不能为空"); + } + Bson filter = Filters.eq(ID, id); + UpdateResult result = getCollection(collectionName).updateOne(filter, update); + if (result.wasAcknowledged()) { + return (result.getModifiedCount() > 0); + } + return true; + } + + public boolean deleteOne(String collectionName, Bson filter) { + DeleteResult result = getCollection(collectionName).deleteOne(filter); + if (result.wasAcknowledged()) { + return (result.getDeletedCount() > 0); + } + return true; + } + + public long deleteAll(String collectionName, Bson filter) { + DeleteResult result = getCollection(collectionName).deleteMany(filter); + if (result.wasAcknowledged()) { + return result.getDeletedCount(); + } + return 0; + } + + public boolean deleteById(String collectionName, UUID id) { + return deleteOne(collectionName, Filters.eq(ID, id)); + } + + public void drop(String collectionName) { + getCollection(collectionName).drop(); + } + + public void createIndex(String collectionName, Bson keys) { + getCollection(collectionName).createIndex(keys); + } + + public void dropIndexes(String collectionName) { + getCollection(collectionName).dropIndexes(); + } + + public void dropIndex(String collectionName, String indexName) { + getCollection(collectionName).dropIndex(indexName); + } + + protected String toJson(Object obj) { + return SerializeHelper.serialize(obj); + } +} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/mongodb/UpdateEntity.java b/src/main/java/com/io/yutian/elementoriginlib/mongodb/UpdateEntity.java new file mode 100644 index 0000000..3da0b95 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/mongodb/UpdateEntity.java @@ -0,0 +1,49 @@ +package com.io.yutian.elementoriginlib.mongodb; + +import com.mongodb.BasicDBObject; + +public class UpdateEntity { + private BasicDBObject incObjects = new BasicDBObject(); + private BasicDBObject setObjects = new BasicDBObject(); + private BasicDBObject unsetObjects = new BasicDBObject(); + + public UpdateEntity() { + } + + public UpdateEntity(BasicDBObject set) { + this.setObjects = set; + } + + public UpdateEntity set(String key, Object value) { + setObjects.append(key, value); + return this; + } + + public UpdateEntity unset(String key) { + unsetObjects.append(key, 0); + return this; + } + + public UpdateEntity inc(String key, int value) { + incObjects.append(key, value); + return this; + } + + public BasicDBObject set() { + return setObjects; + } + + public BasicDBObject unset() { + return unsetObjects; + } + + public BasicDBObject inc() { + return incObjects; + } + + public void reset() { + setObjects.clear(); + incObjects.clear(); + unsetObjects.clear(); + } +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/point/Point.java b/src/main/java/com/io/yutian/elementoriginlib/point/Point.java index e8283b7..60305ad 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/point/Point.java +++ b/src/main/java/com/io/yutian/elementoriginlib/point/Point.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -61,6 +64,23 @@ public class Point { this.z = z; } + public double distance(@NotNull Point o) { + return Math.sqrt(this.distanceSquared(o)); + } + + public double distance(@NotNull Vector o) { + return Math.sqrt(this.distanceSquared(o)); + } + + public double distanceSquared(@NotNull Point o) { + return NumberConversions.square(this.x - o.x) + NumberConversions.square(this.y - o.y) + NumberConversions.square(this.z - o.z); + } + + public double distanceSquared(@NotNull Vector o) { + return NumberConversions.square(this.x - o.getX()) + NumberConversions.square(this.y - o.getY()) + NumberConversions.square(this.z - o.getZ()); + } + + public Point clone() { return new Point(x, y, z); } diff --git a/src/main/java/com/io/yutian/elementoriginlib/point/Region.java b/src/main/java/com/io/yutian/elementoriginlib/point/Region.java index 74bf82c..f40a0b8 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/point/Region.java +++ b/src/main/java/com/io/yutian/elementoriginlib/point/Region.java @@ -1,6 +1,5 @@ package com.io.yutian.elementoriginlib.point; -import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -30,6 +29,13 @@ public class Region { return max; } + public Point getCenter() { + double centerX = (min.getX() + max.getX()) / 2.0; + double centerY = (min.getY() + max.getY()) / 2.0; + double centerZ = (min.getZ() + max.getZ()) / 2.0; + return new Point(centerX, centerY, centerZ); + } + public static Region deserialize(ConfigurationSection section) { World world1 = Bukkit.getWorld(section.getString("world")); Point point1 = Point.deserialize(section.getConfigurationSection("min")); diff --git a/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassLoaderProcessor.java b/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassLoaderProcessor.java new file mode 100644 index 0000000..7ab3365 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassLoaderProcessor.java @@ -0,0 +1,9 @@ +package com.io.yutian.elementoriginlib.scanner; + +public interface ClassLoaderProcessor { + + boolean isMatch(Class loaderClass, byte[] classBytes); + + void process(Class loaderClass, Object instance); + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassScanner.java b/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassScanner.java new file mode 100644 index 0000000..407e1b1 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/scanner/ClassScanner.java @@ -0,0 +1,317 @@ +package com.io.yutian.elementoriginlib.scanner; + +import com.io.yutian.elementoriginlib.logger.Logger; +import com.io.yutian.elementoriginlib.shadowloader.BytecodeRewriter; +import com.io.yutian.elementoriginlib.shadowloader.ShadowClassLoader; +import javassist.ClassPool; +import javassist.CtClass; + +import java.io.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ClassScanner { + + private static final Logger LOGGER = Logger.getLogger(ClassScanner.class); + private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors(); + public static final Set DEFAULT_PACKAGES = new HashSet<>(Arrays.asList("com.io.yutian")); + public static final Set DEFAULT_EXCLUDE_PACKAGES = new HashSet<>(Arrays.asList("com.io.yutian.elementoriginlib")); + + private Set packageNames; + private Set excludePackageNames; + + private ClassLoaderProcessor classLoaderProcessor; + private ClassLoader classLoader; + + private Set> scannedClasses = ConcurrentHashMap.newKeySet(); + private Map scannedClassInstances = new ConcurrentHashMap<>(); + + public ClassScanner(Set packageNames, Set excludePackageNames, ClassLoader classLoader, ClassLoaderProcessor classLoaderProcessor) { + this.packageNames = packageNames; + this.excludePackageNames = excludePackageNames; + this.classLoader = classLoader; + this.classLoaderProcessor = classLoaderProcessor; + } + + public void scan(File pluginsFolder) { + File[] jars = pluginsFolder.listFiles((dir, name) -> name.endsWith(".jar")); + if (jars == null) return; + ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); + + for (File jar : jars) { + executor.submit(() -> { + try { + scanJar(jar); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + }); + } + executor.shutdown(); + try { + executor.awaitTermination(60, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void scanJar(File jarFile) throws IOException { + try (JarFile jar = new JarFile(jarFile)) { + Enumeration entries = jar.entries(); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarFile.toURI().toURL()}, classLoader); + ShadowClassLoader shadowLoader = new ShadowClassLoader(classLoader); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + String className = entry.getName().replace("/", ".").replace(".class", ""); + if (excludePackageNames.stream().anyMatch(className::startsWith)) { + continue; + } + if (packageNames.stream().noneMatch(className::startsWith)) { + continue; + } + try (InputStream is = jar.getInputStream(entry)) { + byte[] originalBytes = is.readAllBytes(); + Class clazz = urlClassLoader.loadClass(className); + if (classLoaderProcessor.isMatch(clazz, originalBytes)) { + String newName = "lib.shadow." + clazz.getName(); + byte[] newBytes = BytecodeRewriter.rewriteClass(originalBytes, className, newName); + Class newClass = shadowLoader.define(newName, newBytes); + Object instance = newClass.newInstance(); + classLoaderProcessor.process(newClass, instance); + scannedClasses.add(clazz); + scannedClassInstances.put(clazz.getName(), instance); + } + } catch (Throwable ignored) { + LOGGER.error(ignored.getMessage(), ignored); + } + } + } + } + } + + public static Map> getClassAnnotations(byte[] classBytes) { + Map> annotations = new HashMap<>(); + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes))) { + in.skipBytes(8); // magic, minor, major + int cpCount = in.readUnsignedShort(); + + Map utf8 = new HashMap<>(); + Map classInfo = new HashMap<>(); + + for (int i = 1; i < cpCount; i++) { + int tag = in.readUnsignedByte(); + switch (tag) { + case 1 -> utf8.put(i, in.readUTF()); + case 3, 4 -> in.skipBytes(4); + case 5, 6 -> { in.skipBytes(8); i++; } + case 7 -> classInfo.put(i, in.readUnsignedShort()); + case 8 -> in.readUnsignedShort(); + case 9, 10, 11, 12, 18 -> in.skipBytes(4); + case 15 -> in.skipBytes(3); + case 16 -> in.skipBytes(2); + case 17 -> in.skipBytes(4); + default -> throw new IOException("Bad constant pool tag: " + tag); + } + } + + in.skipBytes(2 + 2 + 2); // access_flags, this_class, super_class + + int interfacesCount = in.readUnsignedShort(); + in.skipBytes(2 * interfacesCount); + + int fieldsCount = in.readUnsignedShort(); + skipAttributes(in, fieldsCount); + + int methodsCount = in.readUnsignedShort(); + skipAttributes(in, methodsCount); + + int attributesCount = in.readUnsignedShort(); + for (int i = 0; i < attributesCount; i++) { + int nameIndex = in.readUnsignedShort(); + String attrName = utf8.get(nameIndex); + int length = in.readInt(); + if ("RuntimeVisibleAnnotations".equals(attrName)) { + int numAnnotations = in.readUnsignedShort(); + for (int j = 0; j < numAnnotations; j++) { + int typeIndex = in.readUnsignedShort(); + String desc = utf8.get(typeIndex); + if (desc == null) continue; + String annotationClass = desc.substring(1, desc.length() - 1).replace("/", "."); + Map values = readAnnotationValues(in, utf8); + annotations.put(annotationClass, values); + } + } else { + in.skipBytes(length); + } + } + } catch (IOException ignored) { + } + return annotations; + } + + private static void skipAttributes(DataInputStream in, int count) throws IOException { + for (int i = 0; i < count; i++) { + in.skipBytes(6); // access_flags, name_index, descriptor_index + int attributesCount = in.readUnsignedShort(); + for (int j = 0; j < attributesCount; j++) { + in.skipBytes(2); // attribute_name_index + int length = in.readInt(); + in.skipBytes(length); + } + } + } + + private static Map readAnnotationValues(DataInputStream in, Map utf8) throws IOException { + Map map = new HashMap<>(); + int numPairs = in.readUnsignedShort(); + for (int i = 0; i < numPairs; i++) { + int elementNameIndex = in.readUnsignedShort(); + String elementName = utf8.get(elementNameIndex); + Object value = readElementValue(in, utf8); + map.put(elementName, value); + } + return map; + } + + private static Object readElementValue(DataInputStream in, Map utf8) throws IOException { + char tag = (char) in.readUnsignedByte(); + return switch (tag) { + case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's' -> { + int constIndex = in.readUnsignedShort(); + yield utf8.get(constIndex); + } + case 'e' -> { + in.skipBytes(4); + yield ""; + } + case 'c' -> { + in.skipBytes(2); + yield ""; + } + case '@' -> { + skipAnnotation(in, utf8); + yield ""; + } + case '[' -> { + int numValues = in.readUnsignedShort(); + List list = new ArrayList<>(); + for (int i = 0; i < numValues; i++) { + list.add(readElementValue(in, utf8)); + } + yield list; + } + default -> null; + }; + } + + private static void skipAnnotation(DataInputStream in, Map utf8) throws IOException { + in.skipBytes(2); + int numPairs = in.readUnsignedShort(); + for (int i = 0; i < numPairs; i++) { + in.skipBytes(2); + readElementValue(in, utf8); + } + } + + public static String getSuperClassName(byte[] classBytes) { + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes))) { + in.skipBytes(8); // magic + minor + major + int constantPoolCount = in.readUnsignedShort(); + + Map utf8Constants = new HashMap<>(); + Map classInfo = new HashMap<>(); + + for (int i = 1; i < constantPoolCount; i++) { + int tag = in.readUnsignedByte(); + switch (tag) { + case 1 -> utf8Constants.put(i, in.readUTF()); // Utf8 + case 3, 4 -> in.skipBytes(4); + case 5, 6 -> { in.skipBytes(8); i++; } + case 7 -> classInfo.put(i, in.readUnsignedShort()); + case 8 -> in.readUnsignedShort(); + case 9, 10, 11, 12, 18 -> in.skipBytes(4); + case 15 -> in.skipBytes(3); + case 16 -> in.skipBytes(2); + case 17 -> in.skipBytes(4); + default -> throw new IOException("Bad constant pool tag: " + tag); + } + } + + in.skipBytes(2); // access_flags + in.skipBytes(2); // this_class + int superClassIndex = in.readUnsignedShort(); + + Integer nameIndex = classInfo.get(superClassIndex); + if (nameIndex != null) { + return utf8Constants.get(nameIndex).replace("/", "."); + } + } catch (IOException e) { + return null; + } + return null; + } + + public static Set getInterfaceNames(byte[] classBytes) { + Set interfaces = new HashSet<>(); + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes))) { + in.skipBytes(8); // magic + minor + major + int constantPoolCount = in.readUnsignedShort(); + + Map utf8Constants = new HashMap<>(); + Map classInfo = new HashMap<>(); + + for (int i = 1; i < constantPoolCount; i++) { + int tag = in.readUnsignedByte(); + switch (tag) { + case 1 -> utf8Constants.put(i, in.readUTF()); // Utf8 + case 3, 4 -> in.skipBytes(4); // int, float + case 5, 6 -> { in.skipBytes(8); i++; } // long, double + case 7 -> classInfo.put(i, in.readUnsignedShort()); // Class -> name_index + case 8 -> in.readUnsignedShort(); // String + case 9, 10, 11, 12, 18 -> in.skipBytes(4); // Field/Method/InterfaceMethod/NameAndType/InvokeDynamic + case 15 -> in.skipBytes(3); // MethodHandle + case 16 -> in.skipBytes(2); // MethodType + case 17 -> in.skipBytes(4); // Dynamic + default -> throw new IOException("Bad constant pool tag: " + tag); + } + } + + in.skipBytes(2); // access flags + in.skipBytes(2); // this class + in.skipBytes(2); // super class + + int interfacesCount = in.readUnsignedShort(); + for (int i = 0; i < interfacesCount; i++) { + int index = in.readUnsignedShort(); + Integer nameIndex = classInfo.get(index); + if (nameIndex != null) { + String iface = utf8Constants.get(nameIndex); + if (iface != null) { + interfaces.add(iface.replace("/", ".")); + } + } + } + return interfaces; + } catch (IOException e) { + return interfaces; + } + } + + public Set> getScannedClasses() { + return Collections.unmodifiableSet(scannedClasses); + } + + public Map getScannedClassInstances() { + return Collections.unmodifiableMap(scannedClassInstances); + } + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/serialize/SerializeHelper.java b/src/main/java/com/io/yutian/elementoriginlib/serialize/SerializeHelper.java index e8cc1fa..7d9b5d1 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/serialize/SerializeHelper.java +++ b/src/main/java/com/io/yutian/elementoriginlib/serialize/SerializeHelper.java @@ -11,7 +11,6 @@ import com.io.yutian.elementoriginlib.logger.Logger; import com.io.yutian.elementoriginlib.serialize.serializers.*; import org.bukkit.inventory.ItemStack; -import java.util.Objects; import java.util.UUID; public class SerializeHelper { @@ -26,11 +25,11 @@ public class SerializeHelper { objectMapper = new ObjectMapper(); objectMapper.setVisibility( VisibilityChecker.Std.defaultInstance() - .withFieldVisibility(JsonAutoDetect.Visibility.ANY) + .withFieldVisibility(JsonAutoDetect.Visibility.NONE) .withGetterVisibility(JsonAutoDetect.Visibility.NONE) .withSetterVisibility(JsonAutoDetect.Visibility.NONE) .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE) - .withCreatorVisibility(JsonAutoDetect.Visibility.NONE) + .withCreatorVisibility(JsonAutoDetect.Visibility.ANY) ); objectMapper.registerModule(new AfterburnerModule()); diff --git a/src/main/java/com/io/yutian/elementoriginlib/shadowloader/BytecodeRewriter.java b/src/main/java/com/io/yutian/elementoriginlib/shadowloader/BytecodeRewriter.java new file mode 100644 index 0000000..4a633be --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/shadowloader/BytecodeRewriter.java @@ -0,0 +1,16 @@ +package com.io.yutian.elementoriginlib.shadowloader; + +import javassist.ClassPool; +import javassist.CtClass; + +public class BytecodeRewriter { + + public static byte[] rewriteClass(byte[] originalBytes, String originalName, String newName) throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ct = pool.makeClass(new java.io.ByteArrayInputStream(originalBytes)); + ct.setName(newName); + byte[] result = ct.toBytecode(); + ct.detach(); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/shadowloader/ShadowClassLoader.java b/src/main/java/com/io/yutian/elementoriginlib/shadowloader/ShadowClassLoader.java new file mode 100644 index 0000000..3ca77df --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/shadowloader/ShadowClassLoader.java @@ -0,0 +1,12 @@ +package com.io.yutian.elementoriginlib.shadowloader; + +public class ShadowClassLoader extends ClassLoader { + + public ShadowClassLoader(ClassLoader parent) { + super(parent); + } + + public Class define(String name, byte[] bytecode) { + return defineClass(name, bytecode, 0, bytecode.length); + } +} \ No newline at end of file diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/ClassUtil.java b/src/main/java/com/io/yutian/elementoriginlib/util/ClassUtil.java index b075d2d..0c07bcd 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/util/ClassUtil.java +++ b/src/main/java/com/io/yutian/elementoriginlib/util/ClassUtil.java @@ -33,6 +33,13 @@ public class ClassUtil { return false; } + public static Class getWrapperType(Class clazz) { + if (clazz == null) { + return null; + } + return PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz); + } + public static String getJar(Plugin plugin) { String p = plugin.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); return p.substring(1); diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/ColorUtil.java b/src/main/java/com/io/yutian/elementoriginlib/util/ColorUtil.java new file mode 100644 index 0000000..7599b5c --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/util/ColorUtil.java @@ -0,0 +1,314 @@ +package com.io.yutian.elementoriginlib.util; + +import com.google.common.collect.ImmutableMap; +import net.md_5.bungee.api.ChatColor; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; + +public class ColorUtil { + + public static final List SPECIAL_COLORS = Arrays.asList("&l", "&n", "&o", "&k", "&m"); + + private static final Map COLORS = ImmutableMap.builder() + .put(new Color(0), ChatColor.getByChar('0')) + .put(new Color(170), ChatColor.getByChar('1')) + .put(new Color(43520), ChatColor.getByChar('2')) + .put(new Color(43690), ChatColor.getByChar('3')) + .put(new Color(11141120), ChatColor.getByChar('4')) + .put(new Color(11141290), ChatColor.getByChar('5')) + .put(new Color(16755200), ChatColor.getByChar('6')) + .put(new Color(11184810), ChatColor.getByChar('7')) + .put(new Color(5592405), ChatColor.getByChar('8')) + .put(new Color(5592575), ChatColor.getByChar('9')) + .put(new Color(5635925), ChatColor.getByChar('a')) + .put(new Color(5636095), ChatColor.getByChar('b')) + .put(new Color(16733525), ChatColor.getByChar('c')) + .put(new Color(16733695), ChatColor.getByChar('d')) + .put(new Color(16777045), ChatColor.getByChar('e')) + .put(new Color(16777215), ChatColor.getByChar('f')) + .build(); + + private static final List PATTERNS = Arrays.asList(new GradientPattern(), new SolidPattern(), new RainbowPattern()); + + private static final LruCache LRU_CACHE = new LruCache(512); + + @Nonnull + public static String process(@Nonnull String string) { + String result = LRU_CACHE.getResult(string); + if (result != null) { + return result; + } + String input = string; + for (Pattern pattern : PATTERNS) { + string = pattern.process(string); + } + string = ChatColor.translateAlternateColorCodes('&', string); + LRU_CACHE.put(input, string); + return string; + } + + @Nonnull + public static List process(@Nonnull List strings) { + strings.replaceAll(ColorUtil::process); + return strings; + } + + @Nonnull + public static String color(@Nonnull String string, @Nonnull Color color) { + return ChatColor.of(color) + string; + } + + @Nonnull + public static String color(@Nonnull String string, @Nonnull Color start, @Nonnull Color end) { + StringBuilder specialColors = new StringBuilder(); + for (String color : SPECIAL_COLORS) { + if (string.contains(color)) { + specialColors.append(color); + string = string.replace(color, ""); + } + } + StringBuilder stringBuilder = new StringBuilder(); + ChatColor[] colors = createGradient(start, end, string.length()); + String[] characters = string.split(""); + for (int i = 0; i < string.length(); i++) { + stringBuilder.append(colors[i]).append(specialColors).append(characters[i]); + } + return stringBuilder.toString(); + } + + @Nonnull + public static String rainbow(@Nonnull String string, float saturation) { + StringBuilder specialColors = new StringBuilder(); + for (String color : SPECIAL_COLORS) { + if (string.contains(color)) { + specialColors.append(color); + string = string.replace(color, ""); + } + } + StringBuilder stringBuilder = new StringBuilder(); + ChatColor[] colors = createRainbow(string.length(), saturation); + String[] characters = string.split(""); + for (int i = 0; i < string.length(); i++) { + stringBuilder.append(colors[i]).append(specialColors).append(characters[i]); + } + return stringBuilder.toString(); + } + + @Nonnull + public static ChatColor getColor(@Nonnull String string) { + return ChatColor.of(new Color(Integer.parseInt(string, 16))); + } + + @Nonnull + public static String stripColorFormatting(@Nonnull String string) { + return string.replaceAll("[&§][a-f0-9lnokm]|<[/]?\\w{5,8}(:[0-9A-F]{6})?>", ""); + } + + @Nonnull + private static ChatColor[] createRainbow(int step, float saturation) { + ChatColor[] colors = new ChatColor[step]; + double colorStep = (1.00 / step); + for (int i = 0; i < step; i++) { + Color color = Color.getHSBColor((float) (colorStep * i), saturation, saturation); + colors[i] = ChatColor.of(color); + } + return colors; + } + + @Nonnull + private static ChatColor[] createGradient(@Nonnull Color start, @Nonnull Color end, int step) { + if (step <= 1) { + return new ChatColor[]{ChatColor.WHITE, ChatColor.WHITE, ChatColor.WHITE}; + } + + ChatColor[] colors = new ChatColor[step]; + int stepR = Math.abs(start.getRed() - end.getRed()) / (step - 1); + int stepG = Math.abs(start.getGreen() - end.getGreen()) / (step - 1); + int stepB = Math.abs(start.getBlue() - end.getBlue()) / (step - 1); + int[] direction = new int[]{ + start.getRed() < end.getRed() ? +1 : -1, + start.getGreen() < end.getGreen() ? +1 : -1, + start.getBlue() < end.getBlue() ? +1 : -1 + }; + + for (int i = 0; i < step; i++) { + Color color = new Color(start.getRed() + ((stepR * i) * direction[0]), start.getGreen() + ((stepG * i) * direction[1]), start.getBlue() + ((stepB * i) * direction[2])); + colors[i] = ChatColor.of(color); + } + return colors; + } + + + @Nonnull + private static ChatColor getClosestColor(Color color) { + Color nearestColor = null; + double nearestDistance = Integer.MAX_VALUE; + + for (Color constantColor : COLORS.keySet()) { + double distance = Math.pow(color.getRed() - constantColor.getRed(), 2) + Math.pow(color.getGreen() - constantColor.getGreen(), 2) + Math.pow(color.getBlue() - constantColor.getBlue(), 2); + if (nearestDistance > distance) { + nearestColor = constantColor; + nearestDistance = distance; + } + } + return COLORS.get(nearestColor); + } + + @ThreadSafe + private static class LruCache { + + private final Deque deque = new LinkedList<>(); + private final Map map = new ConcurrentHashMap<>(); + + private final int maxSize; + + public LruCache(int maxSize) { + this.maxSize = maxSize; + } + + public String getResult(String input) { + if (input != null && map.containsKey(input)) { + LruElement curr = map.get(input); + synchronized (deque) { + deque.remove(input); + deque.addFirst(input); + } + return curr.getResult(); + } + + return null; + } + + public void put(String input, String result) { + if (input == null || result == null) { + return; + } + synchronized (deque) { + if (map.containsKey(input)) { + deque.remove(input); + } else { + int size = deque.size(); + if (size == maxSize && size > 0) { + String temp = deque.removeLast(); + map.remove(temp); + } + } + LruElement newObj = new LruElement(input, result); + deque.addFirst(input); + map.put(input, newObj); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LruCache lruCache = (LruCache) o; + return maxSize == lruCache.maxSize && deque.equals(lruCache.deque) && map.equals(lruCache.map); + } + + @Override + public int hashCode() { + int result = deque.hashCode(); + result = 31 * result + map.hashCode(); + result = 31 * result + maxSize; + return result; + } + } + + private static class LruElement { + + private final String input; + private final String result; + + public LruElement(String input, String result) { + this.input = input; + this.result = result; + } + + public String getInput() { + return input; + } + + public String getResult() { + return result; + } + } + + private static interface Pattern { + + String process(String string); + + } + + private static class GradientPattern implements Pattern { + + private static final java.util.regex.Pattern PATTERN = java.util.regex.Pattern.compile( + "[<{]#([A-Fa-f0-9]{6})[}>](((?![<{]#[A-Fa-f0-9]{6}[}>]).)*)[<{]/#([A-Fa-f0-9]{6})[}>]" + ); + + public String process(String string) { + Matcher matcher = PATTERN.matcher(string); + while (matcher.find()) { + String start = matcher.group(1); + String content = matcher.group(2); + String end = matcher.group(4); + + string = string.replace( + matcher.group(), + color( + content, + new Color(Integer.parseInt(start, 16)), + new Color(Integer.parseInt(end, 16)) + ) + ); + } + return string; + } + + } + + private static class RainbowPattern implements Pattern { + + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(.*?)"); + + public String process(String string) { + Matcher matcher = pattern.matcher(string); + while (matcher.find()) { + String saturation = matcher.group(1); + String content = matcher.group(2); + + string = string.replace(matcher.group(), rainbow(content, Float.parseFloat(saturation))); + } + return string; + } + + } + + private static class SolidPattern implements Pattern { + + public static final java.util.regex.Pattern PATTERN = java.util.regex.Pattern.compile("[<{]#([A-Fa-f0-9]{6})[}>]|[&]?#([A-Fa-f0-9]{6})"); + + public String process(String string) { + Matcher matcher = PATTERN.matcher(string); + while (matcher.find()) { + String color = matcher.group(1); + if (color == null) { + color = matcher.group(2); + } + + string = string.replace(matcher.group(), getColor(color) + ""); + } + return string; + } + + } + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/ItemStackBuilder.java b/src/main/java/com/io/yutian/elementoriginlib/util/ItemStackBuilder.java index e52b4ac..5d4b2cc 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/util/ItemStackBuilder.java +++ b/src/main/java/com/io/yutian/elementoriginlib/util/ItemStackBuilder.java @@ -1,7 +1,10 @@ package com.io.yutian.elementoriginlib.util; +import com.io.yutian.elementoriginlib.ElementOriginLib; +import com.io.yutian.elementoriginlib.logger.Logger; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import net.kyori.adventure.text.Component; import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeModifier; @@ -10,11 +13,10 @@ import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.FireworkMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.inventory.meta.*; +import org.bukkit.inventory.meta.trim.ArmorTrim; import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.*; @@ -23,168 +25,40 @@ public class ItemStackBuilder { private ItemStack itemStack; - private Material type; - - private int amount; - - private short data; - - private ItemMeta meta; - - private String displayName; - - private int customModelData; - - private List lore; - - private Map enchants; - - private Set itemFlags; - - private List potionEffects; - private List patterns; - private List fireworkEffects; - private String skullTextures; - private UUID skullOwner; - public ItemStackBuilder(Material material) { this.itemStack = new ItemStack(material); - this.type = material; - this.amount = 1; - this.data = 0; - this.meta = this.itemStack.getItemMeta(); - this.displayName = this.meta.getDisplayName(); - this.customModelData = this.meta.hasCustomModelData() ? this.meta.getCustomModelData() : -1; - this.lore = new ArrayList<>(); - this.enchants = new HashMap<>(); - this.itemFlags = new HashSet<>(); - this.potionEffects = new ArrayList<>(); - this.patterns = new ArrayList<>(); - this.fireworkEffects = new ArrayList<>(); } public ItemStackBuilder(ItemStack item) { this.itemStack = item.clone(); - this.type = item.getType(); - this.amount = item.getAmount(); - this.data = item.getDurability(); - this.meta = item.getItemMeta(); - this.displayName = this.meta.getDisplayName(); - this.customModelData = this.meta.hasCustomModelData() ? this.meta.getCustomModelData() : -1; - this.lore = this.meta.getLore() == null ? new ArrayList<>() : meta.getLore(); - this.enchants = this.meta.getEnchants(); - this.itemFlags = this.meta.getItemFlags(); - this.potionEffects = new ArrayList<>(); - this.patterns = new ArrayList<>(); - this.fireworkEffects = new ArrayList<>(); } public ItemStack build() { - ItemStack item = new ItemStack(type, amount); - item.setType(type); - item.setAmount(amount); - ItemMeta itemMeta = item.getItemMeta(); - if (displayName != null) { - itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', displayName)); - } - if (enchants != null) { - item.addUnsafeEnchantments(enchants); - } - if (lore != null) { - List l = new ArrayList<>(); - lore.forEach((s) -> { - l.add(ChatColor.translateAlternateColorCodes('&', s)); - }); - itemMeta.setLore(l); - } - if (this.customModelData != -1) { - itemMeta.setCustomModelData(customModelData); - } - if (itemFlags != null) { - for (ItemFlag flag : itemFlags) { - itemMeta.addItemFlags(flag); - } - } - if (fireworkEffects != null && fireworkEffects.size() > 0 && (type.equals(Material.FIREWORK_STAR) || type.equals(Material.FIREWORK_ROCKET))) { - FireworkMeta fireworkMeta = (FireworkMeta) itemMeta; - for (FireworkEffect effect : fireworkEffects) { - fireworkMeta.addEffect(effect); - } - } - if (skullTextures != null && type.equals(Material.PLAYER_HEAD)) { - SkullMeta skullMeta = (SkullMeta) itemMeta; - String uuid = UUID.randomUUID().toString(); - - GameProfile profile = new GameProfile(UUID.fromString(uuid), null); - profile.getProperties().put("textures", new Property("textures", skullTextures)); - Field profileField; - try { - profileField = skullMeta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(skullMeta, profile); - } catch (NullPointerException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e1) { - } - } else if (skullOwner != null && type.equals(Material.PLAYER_HEAD)) { - SkullMeta skullMeta = (SkullMeta) itemMeta; - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(skullOwner); - if (offlinePlayer != null) { - skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(skullOwner)); - } - } - item.setItemMeta(itemMeta); - return item; + return itemStack; } public int getAmount() { - return amount; + return itemStack.getAmount(); } public Material getType() { - return type; + return itemStack.getType(); } - public List getLore() { - return lore; + public List getLore() { + return itemStack.getItemMeta().lore(); } public int getCustomModelData() { - return customModelData; + return itemStack.getItemMeta().getCustomModelData(); } public Map getEnchants() { - return enchants; + return itemStack.getItemMeta().getEnchants(); } public Set getItemFlags() { - return itemFlags; - } - - public List getPotionEffects() { - return potionEffects; - } - - public List getFireworkEffects() { - return fireworkEffects; - } - - public List getPatterns() { - return patterns; - } - - public String getSkullTextures() { - return skullTextures; - } - - public String getDisplayName() { - return displayName; - } - - public short getData() { - return data; - } - - public UUID getSkullOwner() { - return skullOwner; + return itemStack.getItemMeta().getItemFlags(); } public ItemStackBuilder setItemStack(ItemStack item) { @@ -193,189 +67,285 @@ public class ItemStackBuilder { } public ItemStackBuilder setType(Material type) { - this.type = type; + this.itemStack.withType(type); return this; } public ItemStackBuilder setCustomModelData(int customModelData) { - this.customModelData = customModelData; + this.itemStack.editMeta((meta)-> meta.setCustomModelData(customModelData)); return this; } public ItemStackBuilder setAmount(int amount) { - this.amount = amount; - return this; - } - - public ItemStackBuilder setData(int data) { - if (data > 255) { - data = 255; - } - this.data = (short) data; - return this; - } - - public ItemStackBuilder setData(short data) { - this.data = data; - return this; - } - - public ItemStackBuilder setItemMeta(ItemMeta meta) { - this.meta = meta; + this.itemStack.setAmount(amount); return this; } public ItemStackBuilder setDisplayName(String name) { - this.displayName = name; + this.itemStack.editMeta((meta)-> meta.displayName(Component.text(name))); + return this; + } + + public ItemStackBuilder setDisplayName(Component name) { + this.itemStack.editMeta((meta)-> meta.displayName(name)); return this; } public ItemStackBuilder setLore(List loreList) { - this.lore = loreList; + List loreComponents = new ArrayList<>(); + for (String lore : loreList) { + loreComponents.add(Component.text(lore)); + } + this.itemStack.editMeta((meta)-> meta.lore(loreComponents)); return this; } public ItemStackBuilder setLore(String... lores) { - this.lore = new ArrayList<>(Arrays.asList(lores)); - return this; + return setLore(List.of(lores)); } public ItemStackBuilder addLore(String lore) { - this.lore.add(lore); + List loreComponents = itemStack.getItemMeta().hasLore() ? itemStack.getItemMeta().lore() : new ArrayList<>(); + loreComponents.add(Component.text(lore)); + this.itemStack.editMeta((meta)-> meta.lore(loreComponents)); return this; } public ItemStackBuilder addLore(String... lores) { - this.lore.addAll(Arrays.asList(lores)); + List loreComponents = itemStack.getItemMeta().hasLore() ? itemStack.getItemMeta().lore() : new ArrayList<>(); + for (String lore : lores) { + loreComponents.add(Component.text(lore)); + } + this.itemStack.editMeta((meta)-> meta.lore(loreComponents)); return this; } - public ItemStackBuilder addLore(List lores) { - for (String lore : lores) { - this.lore.add(lore); + public ItemStackBuilder addLore(Component... lores) { + List loreComponents = itemStack.getItemMeta().hasLore() ? itemStack.getItemMeta().lore() : new ArrayList<>(); + for (Component lore : lores) { + loreComponents.add(lore); } + this.itemStack.editMeta((meta)-> meta.lore(loreComponents)); + return this; + } + + public ItemStackBuilder addLore(List lores) { + List loreComponents = itemStack.getItemMeta().hasLore() ? itemStack.getItemMeta().lore() : new ArrayList<>(); + loreComponents.addAll(lores); + this.itemStack.editMeta((meta)-> meta.lore(loreComponents)); return this; } public ItemStackBuilder addAttributeModifier(Attribute attribute, AttributeModifier attributeModifier) { - this.meta.addAttributeModifier(attribute, attributeModifier); + this.itemStack.editMeta((meta)-> meta.addAttributeModifier(attribute, attributeModifier)); return this; } public ItemStackBuilder removeAttributeModifier(Attribute attribute) { - this.meta.removeAttributeModifier(attribute); + this.itemStack.editMeta((meta)-> meta.removeAttributeModifier(attribute)); return this; } public ItemStackBuilder removeAttributeModifier(EquipmentSlot equipmentSlot) { - this.meta.removeAttributeModifier(equipmentSlot); + this.itemStack.editMeta((meta)-> meta.removeAttributeModifier(equipmentSlot)); return this; } public ItemStackBuilder removeAttributeModifier(Attribute attribute, AttributeModifier attributeModifier) { - this.meta.removeAttributeModifier(attribute, attributeModifier); + this.itemStack.editMeta((meta)-> meta.removeAttributeModifier(attribute, attributeModifier)); return this; } public ItemStackBuilder addEnchant(Enchantment ench, int level) { - this.enchants.put(ench, level); + this.itemStack.editMeta((meta)-> meta.addEnchant(ench, level, true)); return this; } public ItemStackBuilder removeEnchant(Enchantment ench) { - this.enchants.remove(ench); + this.itemStack.editMeta((meta)-> meta.removeEnchant(ench)); return this; } public ItemStackBuilder setEnchants(Map enchants) { - this.enchants = enchants; - return this; - } - - public ItemStackBuilder setEnchants(List enchants) { - this.enchants = getEnchantsFromList(enchants); + this.itemStack.editMeta((meta)->{ + meta.removeEnchantments(); + for (Map.Entry entry : enchants.entrySet()) { + meta.addEnchant(entry.getKey(), entry.getValue(), true); + } + }); return this; } public ItemStackBuilder setItemFlags(Set itemFlags) { - this.itemFlags = itemFlags; + this.itemStack.editMeta((meta)-> { + meta.removeItemFlags(ItemFlag.values()); + for (ItemFlag itemFlag : itemFlags) { + meta.addItemFlags(itemFlag); + } + }); return this; } public ItemStackBuilder addItemFlags(ItemFlag[] itemFlags) { - this.itemFlags.addAll(Arrays.asList(itemFlags)); + this.itemStack.editMeta((meta)-> { + meta.removeItemFlags(ItemFlag.values()); + for (ItemFlag itemFlag : itemFlags) { + meta.addItemFlags(itemFlag); + } + }); return this; } public ItemStackBuilder addItemFlags(ItemFlag itemFlag) { - this.itemFlags.add(itemFlag); + this.itemStack.editMeta((meta)-> meta.addItemFlags(itemFlag)); + return this; + } + + public ItemStackBuilder setUnbreakable(boolean unbreakable) { + this.itemStack.editMeta((meta)-> meta.setUnbreakable(unbreakable)); + return this; + } + + public ItemStackBuilder setHideTooltip(boolean hideTooltip) { + this.itemStack.editMeta((meta)-> meta.setHideTooltip(hideTooltip)); + return this; + } + + public ItemStackBuilder setFireResistant(boolean fireResistant) { + this.itemStack.editMeta((meta)-> meta.setFireResistant(fireResistant)); + return this; + } + + public ItemStackBuilder setMaxStackSize(int maxStackSize) { + this.itemStack.editMeta((meta)-> meta.setCustomModelData(maxStackSize)); + return this; + } + + public ItemStackBuilder setEnchantmentGlint(boolean enchantmentGlint) { + this.itemStack.editMeta((meta)-> meta.setEnchantmentGlintOverride(enchantmentGlint)); return this; } public ItemStackBuilder addPotionEffect(PotionEffect potionEffect) { - this.potionEffects.add(potionEffect); + if (!(itemStack.getItemMeta() instanceof PotionMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + PotionMeta.class.getName()); + return this; + } + this.itemStack.editMeta(PotionMeta.class, (meta)-> meta.addCustomEffect(potionEffect, true)); + return this; + } + + public ItemStackBuilder removePotionEffect(PotionEffect potionEffect) { + if (!(itemStack.getItemMeta() instanceof PotionMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + PotionMeta.class.getName()); + return this; + } + this.itemStack.editMeta(PotionMeta.class, (meta)-> meta.removeCustomEffect(potionEffect.getType())); + return this; + } + + public ItemStackBuilder clearPotionEffects() { + if (!(itemStack.getItemMeta() instanceof PotionMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + PotionMeta.class.getName()); + return this; + } + this.itemStack.editMeta(PotionMeta.class, (meta)-> { + meta.clearCustomEffects(); + }); return this; } public ItemStackBuilder addPattern(Pattern pattern) { - this.patterns.add(pattern); + if (!(itemStack.getItemMeta() instanceof BannerMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + BannerMeta.class.getName()); + return this; + } + this.itemStack.editMeta(BannerMeta.class, (meta)-> meta.addPattern(pattern)); return this; } - public ItemStackBuilder addFireworkEffect(FireworkEffect fireworkEffect) { - this.fireworkEffects.add(fireworkEffect); + public ItemStackBuilder removePattern(int index) { + if (!(itemStack.getItemMeta() instanceof BannerMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + BannerMeta.class.getName()); + return this; + } + this.itemStack.editMeta(BannerMeta.class, (meta)-> meta.removePattern(index)); + return this; + } + + public ItemStackBuilder setPatterns(List patterns) { + if (!(itemStack.getItemMeta() instanceof BannerMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + BannerMeta.class.getName()); + return this; + } + this.itemStack.editMeta(BannerMeta.class, (meta)-> meta.setPatterns(patterns)); + return this; + } + + public ItemStackBuilder setFireworkEffect(FireworkEffect fireworkEffect) { + if (!(itemStack.getItemMeta() instanceof FireworkMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + FireworkMeta.class.getName()); + return this; + } + this.itemStack.editMeta(FireworkMeta.class, (meta)-> meta.addEffect(fireworkEffect)); + return this; + } + + public ItemStackBuilder clearFireworkEffects() { + if (!(itemStack.getItemMeta() instanceof FireworkMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + FireworkMeta.class.getName()); + return this; + } + this.itemStack.editMeta(FireworkMeta.class, (meta)-> meta.clearEffects()); return this; } public ItemStackBuilder setSkullTextures(String skullTextures) { - this.skullTextures = skullTextures; + if (!(itemStack.getItemMeta() instanceof SkullMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + SkullMeta.class.getName()); + return this; + } + this.itemStack.editMeta(SkullMeta.class, (meta)-> { + String uuid = UUID.randomUUID().toString(); + + GameProfile profile = new GameProfile(UUID.fromString(uuid), "Skull"); + profile.getProperties().put("textures", new Property("textures", skullTextures)); + Field profileField; + try { + profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(meta, profile); + } catch (NullPointerException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e1) { + } + }); return this; } - public ItemStackBuilder setSkullOwner(UUID skullOwner) { - this.skullOwner = skullOwner; + public ItemStackBuilder setSkullOwner(String skullOwner) { + if (!(itemStack.getItemMeta() instanceof SkullMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + SkullMeta.class.getName()); + return this; + } + this.itemStack.editMeta(SkullMeta.class, (meta)-> meta.setOwningPlayer(Bukkit.getOfflinePlayer(skullOwner))); return this; } - protected Map getEnchantsFromList(List enchants) { - Map map = new HashMap<>(); - if (enchants == null) { - return map; + public ItemStackBuilder setColor(Color color) { + if (!(itemStack.getItemMeta() instanceof LeatherArmorMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + LeatherArmorMeta.class.getName()); + return this; } - for (String s : enchants) { - if (s.contains(":")) { - String[] part = s.split(":"); - Enchantment en = Enchantment.getByName(part[0]); - if (en == null) { - continue; - } - map.put(en, Integer.parseInt(part[1])); - } - } - return map; + this.itemStack.editMeta(LeatherArmorMeta.class, (meta)-> meta.setColor(color)); + return this; } - protected static ItemStack setEnchants(ItemStack stack, List enchants) { - if (enchants == null) { - return stack; + public ItemStackBuilder setTrim(ArmorTrim trim) { + if (!(itemStack.getItemMeta() instanceof ArmorMeta)) { + ElementOriginLib.LOGGER.warn("ItemMeta is not " + ArmorMeta.class.getName()); + return this; } - for (String s : enchants) { - if (s.contains(":")) { - String[] part = s.split(":"); - Enchantment en = Enchantment.getByName(part[0]); - if (en == null) { - continue; - } - if (stack.getType() != Material.ENCHANTED_BOOK) { - stack.addUnsafeEnchantment(en, Integer.parseInt(part[1])); - } else { - EnchantmentStorageMeta esm = (EnchantmentStorageMeta) stack.getItemMeta(); - esm.addStoredEnchant(en, Integer.parseInt(part[1]), true); - stack.setItemMeta((ItemMeta) esm); - } - } - } - return stack; + this.itemStack.editMeta(ArmorMeta.class, (meta)-> meta.setTrim(trim)); + return this; } private void setField(Object instance, String name, Object value) throws ReflectiveOperationException { diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/LangUtil.java b/src/main/java/com/io/yutian/elementoriginlib/util/LangUtil.java index d7624c4..b71c3e0 100644 --- a/src/main/java/com/io/yutian/elementoriginlib/util/LangUtil.java +++ b/src/main/java/com/io/yutian/elementoriginlib/util/LangUtil.java @@ -1,19 +1,19 @@ package com.io.yutian.elementoriginlib.util; import com.io.yutian.elementoriginlib.ElementOriginLib; -import com.io.yutian.elementoriginlib.expiringmap.ExpirationPolicy; -import com.io.yutian.elementoriginlib.expiringmap.ExpiringMap; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; public class LangUtil { - private static ExpiringMap expiringMap = ExpiringMap.builder().expiration(3, TimeUnit.MINUTES).expirationPolicy(ExpirationPolicy.CREATED).build(); + private static Map expiringMap = new HashMap<>(); private static JSONObject jsonObject = new JSONObject(); public static String getLang(String key) { diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/SkullBuilder.java b/src/main/java/com/io/yutian/elementoriginlib/util/SkullBuilder.java deleted file mode 100644 index 9fdbffc..0000000 --- a/src/main/java/com/io/yutian/elementoriginlib/util/SkullBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.io.yutian.elementoriginlib.util; - -import org.bukkit.Material; - -import java.util.UUID; - -public class SkullBuilder extends ItemStackBuilder { - - public SkullBuilder() { - super(Material.PLAYER_HEAD); - } - - public SkullBuilder(String textures) { - super(Material.PLAYER_HEAD); - setSkullTextures(textures); - } - - public SkullBuilder(UUID uuid) { - super(Material.PLAYER_HEAD); - setSkullOwner(uuid); - } - -} diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/StatDataUtil.java b/src/main/java/com/io/yutian/elementoriginlib/util/StatDataUtil.java new file mode 100644 index 0000000..84b888b --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/util/StatDataUtil.java @@ -0,0 +1,347 @@ +package com.io.yutian.elementoriginlib.util; + +import com.io.yutian.elementoriginlib.exception.ItemStatDataLoadException; +import com.io.yutian.elementoriginlib.item.stat.data.*; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class StatDataUtil { + + public static BooleanData loadBooleanDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof Boolean bool) { + return new BooleanData(bool); + } else if (value instanceof String arg) { + boolean parsed = Boolean.parseBoolean(arg); + if (!arg.equalsIgnoreCase("true") && !arg.equalsIgnoreCase("false")) { + throw new ItemStatDataLoadException("Invalid boolean string: " + arg); + } + return new BooleanData(parsed); + } else if (value instanceof Byte b) { + return new BooleanData(b == 1); + } else if (value instanceof Integer i) { + return new BooleanData(i == 1); + } else { + throw new ItemStatDataLoadException("Invalid boolean value: " + value); + } + } catch (ClassCastException e) { + throw new ItemStatDataLoadException("Invalid type: " + value.getClass(), e); + } + } + + public static DoubleListData loadDoubleListDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof List list) { + List doubleList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Double d) { + doubleList.add(d); + } else if (item instanceof String str) { + try { + doubleList.add(Double.parseDouble(str)); + } catch (NumberFormatException e) { + throw new ItemStatDataLoadException("Invalid string value for Double: " + str, e); + } + } else { + throw new ItemStatDataLoadException("List contains non-double element: " + item); + } + } + return new DoubleListData(doubleList); + } else if (value instanceof double[] array) { + List doubleList = new ArrayList<>(); + for (double d : array) { + doubleList.add(d); + } + return new DoubleListData(doubleList); + } else if (value instanceof Double[] array) { + List doubleList = new ArrayList<>(Arrays.asList(array)); + return new DoubleListData(doubleList); + } else if (value instanceof String str) { + List doubleList = new ArrayList<>(); + String[] items = str.split(","); + for (String item : items) { + try { + doubleList.add(Double.parseDouble(item.trim())); + } catch (NumberFormatException e) { + throw new ItemStatDataLoadException("Invalid number format in string: " + item, e); + } + } + return new DoubleListData(doubleList); + } else { + throw new ItemStatDataLoadException("Invalid value type for DoubleListData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static DoubleData loadDoubleDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof Double d) { + return new DoubleData(d); + } else if (value instanceof String arg) { + try { + return new DoubleData(Double.parseDouble(arg)); + } catch (NumberFormatException e) { + throw new ItemStatDataLoadException("Invalid double string: " + arg, e); + } + } else if (value instanceof Byte b) { + return new DoubleData((double) b); + } else if (value instanceof Integer i) { + return new DoubleData((double) i); + } else if (value instanceof Long l) { + return new DoubleData((double) l); + } else { + throw new ItemStatDataLoadException("Invalid double value: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static EnumListData loadEnumListDataAsObject(Class clazz, @NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof List list) { + List enumList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof String str) { + try { + E enumConstant = (E) Enum.valueOf(clazz, str); + enumList.add(enumConstant); + } catch (IllegalArgumentException e) { + throw new ItemStatDataLoadException("Invalid enum string: " + str, e); + } + } else if (item instanceof Integer i) { + E[] enumConstants = clazz.getEnumConstants(); + if (i < 0 || i >= enumConstants.length) { + throw new ItemStatDataLoadException("Invalid enum ordinal: " + i); + } + enumList.add(enumConstants[i]); + } else if (item instanceof Enum enumItem) { + enumList.add((E) enumItem); + } else { + throw new ItemStatDataLoadException("Invalid list element type: " + item); + } + } + return new EnumListData<>(enumList); + } else if (value instanceof String str) { + List enumList = new ArrayList<>(); + String[] enumNames = str.split(","); + for (String enumName : enumNames) { + try { + E enumConstant = (E) Enum.valueOf(clazz, enumName.trim()); + enumList.add(enumConstant); + } catch (IllegalArgumentException e) { + throw new ItemStatDataLoadException("Invalid enum string: " + enumName, e); + } + } + return new EnumListData<>(enumList); + } else { + throw new ItemStatDataLoadException("Invalid value type for EnumListData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static EnumData loadEnumDataAsObject(Class clazz, @NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof Enum enumValue) { + return new EnumData<>((E) enumValue); + } else if (value instanceof String str) { + try { + E enumConstant = (E) Enum.valueOf(clazz, str); + return new EnumData<>(enumConstant); + } catch (IllegalArgumentException e) { + throw new ItemStatDataLoadException("Invalid enum string: " + str, e); + } + } else if (value instanceof Integer i) { + E[] enumConstants = clazz.getEnumConstants(); + if (i < 0 || i >= enumConstants.length) { + throw new ItemStatDataLoadException("Invalid enum ordinal: " + i); + } + return new EnumData<>(enumConstants[i]); + } else { + throw new ItemStatDataLoadException("Invalid value type for EnumData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static IntListData loadIntListDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof List list) { + List intList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Integer i) { + intList.add(i); + } else { + throw new ItemStatDataLoadException("List contains non-integer element: " + item); + } + } + return new IntListData(intList); + } else if (value instanceof int[] array) { + List intList = new ArrayList<>(); + for (int i : array) { + intList.add(i); + } + return new IntListData(intList); + } else if (value instanceof String str) { + try { + List intList = new ArrayList<>(); + String[] items = str.split(","); + for (String item : items) { + intList.add(Integer.parseInt(item.trim())); + } + return new IntListData(intList); + } catch (NumberFormatException e) { + throw new ItemStatDataLoadException("Invalid integer string: " + str, e); + } + } else { + throw new ItemStatDataLoadException("Invalid value type for IntListData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static IntData loadIntDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof Integer i) { + return new IntData(i); + } else if (value instanceof String arg) { + try { + return new IntData(Integer.parseInt(arg)); + } catch (NumberFormatException e) { + throw new ItemStatDataLoadException("Invalid integer string: " + arg, e); + } + } else if (value instanceof Byte b) { + return new IntData((int) b); + } else if (value instanceof Long l) { + if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { + throw new ItemStatDataLoadException("Long value out of range for integer: " + l); + } + return new IntData(l.intValue()); + } else if (value instanceof Double d) { + if (d < Integer.MIN_VALUE || d > Integer.MAX_VALUE) { + throw new ItemStatDataLoadException("Double value out of range for integer: " + d); + } + return new IntData((int) d.doubleValue()); + } else if (value instanceof Float f) { + if (f < Integer.MIN_VALUE || f > Integer.MAX_VALUE) { + throw new ItemStatDataLoadException("Float value out of range for integer: " + f); + } + return new IntData((int) f.floatValue()); + } else { + throw new ItemStatDataLoadException("Invalid integer value: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static MapData loadMapDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof Map map) { + Map resultMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() instanceof String key) { + resultMap.put(key, entry.getValue()); + } else { + throw new ItemStatDataLoadException("Map key is not a String: " + entry.getKey()); + } + } + return new MapData(resultMap); + } else { + throw new ItemStatDataLoadException("Invalid value type for MapData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static StringListData loadStringListDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof List list) { + List stringList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof String str) { + stringList.add(str); + } else { + throw new ItemStatDataLoadException("List contains non-string element: " + item); + } + } + return new StringListData(stringList); + } else if (value instanceof String[] array) { + // 处理 String[] 类型 + return new StringListData(Arrays.asList(array)); + } else if (value instanceof String str) { + List stringList = new ArrayList<>(); + stringList.add(str); + return new StringListData(stringList); + } else { + throw new ItemStatDataLoadException("Invalid value type for StringListData: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + + public static StringData loadStringDataAsObject(@NotNull Object value) { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + return new StringData(value.toString()); + } + + public static UUIDData loadUUIDDataAsObject(@NotNull Object value) throws ItemStatDataLoadException { + if (Objects.isNull(value)) { + throw new ItemStatDataLoadException("Invalid value: null"); + } + try { + if (value instanceof UUID uuid) { + return new UUIDData(uuid); + } else if (value instanceof String str) { + try { + return new UUIDData(UUID.fromString(str)); + } catch (IllegalArgumentException e) { + throw new ItemStatDataLoadException("Invalid UUID string: " + str, e); + } + } else { + throw new ItemStatDataLoadException("Invalid UUID value: " + value); + } + } catch (Exception e) { + throw new ItemStatDataLoadException(e); + } + } + +} diff --git a/src/main/java/com/io/yutian/elementoriginlib/util/Util.java b/src/main/java/com/io/yutian/elementoriginlib/util/Util.java new file mode 100644 index 0000000..52928c9 --- /dev/null +++ b/src/main/java/com/io/yutian/elementoriginlib/util/Util.java @@ -0,0 +1,26 @@ +package com.io.yutian.elementoriginlib.util; + +import java.util.List; +import java.util.function.Predicate; + +public class Util { + + public static Predicate allOf(List> predicates) { + List> list = List.copyOf(predicates); + + return switch (list.size()) { + case 0 -> object -> true; + case 1 -> list.get(0); + case 2 -> list.get(0).and(list.get(1)); + default -> object -> { + for (Predicate predicate : list) { + if (!predicate.test(object)) { + return false; + } + } + return true; + }; + }; + } + +} diff --git a/src/main/resources/mangodb.yml b/src/main/resources/mangodb.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ceb4d50..16700a0 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,4 +5,4 @@ api-version: '1.20' authors: [ SuperYuTian ] commands: elementoriginlib: - aliases: [ eol ] \ No newline at end of file + aliases: [ eolib ] \ No newline at end of file